From dc31630169af4069cb06fe37fd01d67d467ed63e Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Mon, 22 Jan 2024 04:31:43 +0530 Subject: [PATCH 01/23] feat: adds visualization of NAGs on the SVG board. --- chess/svg.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/chess/svg.py b/chess/svg.py index d3d19e89e..fa740ca3f 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -11,6 +11,7 @@ SQUARE_SIZE = 45 MARGIN = 20 +NAG_SIZE = 15 PIECES = { "b": """""", # noqa: E501 @@ -46,6 +47,39 @@ "h": """""", # noqa: E501 } +NAGS = { + # "!" + "1": """ + + + """, + # "?" + "2": """ + + + """, + # "!!" + "3": """ + + + + """, + # "??" + "4": """ + + + + + + """, + # "?!" + "6": """ + + + + """ +} + XX = """""" # noqa: E501 CHECK_GRADIENT = """""" # noqa: E501 @@ -226,7 +260,8 @@ def board(board: Optional[chess.BaseBoard] = None, *, colors: Dict[str, str] = {}, flipped: bool = False, borders: bool = False, - style: Optional[str] = None) -> str: + style: Optional[str] = None, + nag:Optional[int] = None) -> str: """ Renders a board with pieces and/or selected squares as an SVG image. @@ -256,6 +291,8 @@ def board(board: Optional[chess.BaseBoard] = None, *, :param borders: Pass ``True`` to enable a border around the board and, (if *coordinates* is enabled) the coordinate margin. :param style: A CSS stylesheet to include in the SVG image. + :param nag: Pass ``NAG Constant`` to show Numerical Notation Glyphs (NAGs). + (requires ``lastmove`` to be passed along as argument) >>> import chess >>> import chess.svg @@ -514,4 +551,24 @@ def board(board: Optional[chess.BaseBoard] = None, *, "class": "arrow", })) + if nag is not None and lastmove is not None: + ele = ET.fromstring(NAGS[str(nag)]) + defs.append(ele) + id = ele.attrib.get("id") + file_index = chess.square_file(lastmove.to_square) + rank_index = chess.square_rank(lastmove.to_square) + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + margin + from_file_index = chess.square_file(lastmove.from_square) + # Making sure the NAGs doesn't overlap the Last Move Arrow by switching + # between top left and top right corner depending upon where the Arrow + # is coming from. + x = x + (SQUARE_SIZE - NAG_SIZE if file_index >= from_file_index else 0) + ET.SubElement(svg, "use", _attrs({ + "href": f"#{id}", + "xlink:href": f"#{id}", + "x": x, + "y": y, + })) + return SvgWrapper(ET.tostring(svg).decode("utf-8")) From a75d0288c08a239fc18e7d9489ec9fe5caedfe47 Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Mon, 22 Jan 2024 04:34:29 +0530 Subject: [PATCH 02/23] fix: refactored to remove unneccessary if statements without changing the behaviour --- chess/svg.py | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/chess/svg.py b/chess/svg.py index fa740ca3f..f6c214598 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -319,6 +319,7 @@ def board(board: Optional[chess.BaseBoard] = None, *, margin = 15 if coordinates else 0 full_size = 2 * outer_border + 2 * margin + 2 * inner_border + 8 * SQUARE_SIZE svg = _svg(full_size, size) + defs = ET.SubElement(svg, "defs") if style: ET.SubElement(svg, "style").text = style @@ -327,9 +328,6 @@ def board(board: Optional[chess.BaseBoard] = None, *, desc = ET.SubElement(svg, "desc") asciiboard = ET.SubElement(desc, "pre") asciiboard.text = str(board) - - defs = ET.SubElement(svg, "defs") - if board: for piece_color in chess.COLORS: for piece_type in chess.PIECE_TYPES: if board.pieces_mask(piece_type, piece_color): @@ -339,9 +337,6 @@ def board(board: Optional[chess.BaseBoard] = None, *, if squares: defs.append(ET.fromstring(XX)) - if check is not None: - defs.append(ET.fromstring(CHECK_GRADIENT)) - if outer_border: outer_border_color, outer_border_opacity = _select_color(colors, "outer border") ET.SubElement(svg, "rect", _attrs({ @@ -437,6 +432,7 @@ def board(board: Optional[chess.BaseBoard] = None, *, # Render check mark. if check is not None: + defs.append(ET.fromstring(CHECK_GRADIENT)) file_index = chess.square_file(check) rank_index = chess.square_rank(check) @@ -453,14 +449,14 @@ def board(board: Optional[chess.BaseBoard] = None, *, })) # Render pieces and selected squares. - for square, bb in enumerate(chess.BB_SQUARES): - file_index = chess.square_file(square) - rank_index = chess.square_rank(square) + if board is not None: + for square, bb in enumerate(chess.BB_SQUARES): + file_index = chess.square_file(square) + rank_index = chess.square_rank(square) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + margin + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + margin - if board is not None: piece = board.piece_at(square) if piece: href = f"#{chess.COLOR_NAMES[piece.color]}-{chess.PIECE_NAMES[piece.piece_type]}" @@ -470,14 +466,14 @@ def board(board: Optional[chess.BaseBoard] = None, *, "transform": f"translate({x:d}, {y:d})", }) - # Render selected squares. - if square in squares: - ET.SubElement(svg, "use", _attrs({ - "href": "#xx", - "xlink:href": "#xx", - "x": x, - "y": y, - })) + # Render selected squares. + if square in squares: + ET.SubElement(svg, "use", _attrs({ + "href": "#xx", + "xlink:href": "#xx", + "x": x, + "y": y, + })) # Render arrows. for arrow in arrows: From 9c1d7f60241553ed7234d46239cd1660e071405c Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Tue, 23 Jan 2024 02:45:42 +0530 Subject: [PATCH 03/23] fix: separated rendering of X marked Squares from Piece rendering --- chess/svg.py | 46 +++++++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/chess/svg.py b/chess/svg.py index f6c214598..08a184990 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -324,19 +324,6 @@ def board(board: Optional[chess.BaseBoard] = None, *, if style: ET.SubElement(svg, "style").text = style - if board: - desc = ET.SubElement(svg, "desc") - asciiboard = ET.SubElement(desc, "pre") - asciiboard.text = str(board) - for piece_color in chess.COLORS: - for piece_type in chess.PIECE_TYPES: - if board.pieces_mask(piece_type, piece_color): - defs.append(ET.fromstring(PIECES[chess.Piece(piece_type, piece_color).symbol()])) - - squares = chess.SquareSet(squares) if squares else chess.SquareSet() - if squares: - defs.append(ET.fromstring(XX)) - if outer_border: outer_border_color, outer_border_opacity = _select_color(colors, "outer border") ET.SubElement(svg, "rect", _attrs({ @@ -450,6 +437,15 @@ def board(board: Optional[chess.BaseBoard] = None, *, # Render pieces and selected squares. if board is not None: + desc = ET.SubElement(svg, "desc") + asciiboard = ET.SubElement(desc, "pre") + asciiboard.text = str(board) + # Defining pieces + for piece_color in chess.COLORS: + for piece_type in chess.PIECE_TYPES: + if board.pieces_mask(piece_type, piece_color): + defs.append(ET.fromstring(PIECES[chess.Piece(piece_type, piece_color).symbol()])) + # Rendering pieces for square, bb in enumerate(chess.BB_SQUARES): file_index = chess.square_file(square) rank_index = chess.square_rank(square) @@ -466,14 +462,22 @@ def board(board: Optional[chess.BaseBoard] = None, *, "transform": f"translate({x:d}, {y:d})", }) - # Render selected squares. - if square in squares: - ET.SubElement(svg, "use", _attrs({ - "href": "#xx", - "xlink:href": "#xx", - "x": x, - "y": y, - })) + # Render X Squares + if squares is not None: + defs.append(ET.fromstring(XX)) + squares = chess.SquareSet(squares) if squares else chess.SquareSet() + for square in squares: + file_index = chess.square_file(square) + rank_index = chess.square_rank(square) + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + margin + # Render selected squares + ET.SubElement(svg, "use", _attrs({ + "href": "#xx", + "xlink:href": "#xx", + "x": x, + "y": y, + })) # Render arrows. for arrow in arrows: From 4a32115b2cadb5daa3459e7265c99edf81860ae6 Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Wed, 24 Jan 2024 00:11:29 +0530 Subject: [PATCH 04/23] fixed the order in which ascii board appeared in the svg --- chess/svg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chess/svg.py b/chess/svg.py index 08a184990..0b104fab7 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -319,6 +319,7 @@ def board(board: Optional[chess.BaseBoard] = None, *, margin = 15 if coordinates else 0 full_size = 2 * outer_border + 2 * margin + 2 * inner_border + 8 * SQUARE_SIZE svg = _svg(full_size, size) + desc = ET.SubElement(svg, "desc") defs = ET.SubElement(svg, "defs") if style: @@ -437,7 +438,6 @@ def board(board: Optional[chess.BaseBoard] = None, *, # Render pieces and selected squares. if board is not None: - desc = ET.SubElement(svg, "desc") asciiboard = ET.SubElement(desc, "pre") asciiboard.text = str(board) # Defining pieces From eb13080335e140fb751d782663601d5515f54a17 Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Wed, 24 Jan 2024 02:43:49 +0530 Subject: [PATCH 05/23] fix: added a check where a valid nag was passed but there is no Glyph for it --- chess/svg.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chess/svg.py b/chess/svg.py index 0b104fab7..14ee87295 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -551,7 +551,9 @@ def board(board: Optional[chess.BaseBoard] = None, *, "class": "arrow", })) - if nag is not None and lastmove is not None: + if nag is not None and \ + lastmove is not None and \ + NAGS.get(str(nag), None) is not None: ele = ET.fromstring(NAGS[str(nag)]) defs.append(ele) id = ele.attrib.get("id") From b872884bdcb28fc5b0e13355d59ce70d31f177fd Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Wed, 24 Jan 2024 02:44:42 +0530 Subject: [PATCH 06/23] fix: Updated the ?! Glyph background color for it to be more visible --- chess/svg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chess/svg.py b/chess/svg.py index 14ee87295..2a19167ca 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -74,7 +74,7 @@ """, # "?!" "6": """ - + """ From 368eb5b83e9cc78b18d1e56f8cb1c40bec00a22b Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Wed, 24 Jan 2024 02:45:35 +0530 Subject: [PATCH 07/23] adds basic tests for the squares and NAGs --- test.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test.py b/test.py index e1d0d50c5..ca63f19df 100755 --- a/test.py +++ b/test.py @@ -4299,6 +4299,19 @@ def test_svg_piece(self): svg = chess.svg.piece(chess.Piece.from_symbol("K")) self.assertIn("id=\"white-king\"", svg) + def test_svg_squares(self): + svg = chess.svg.board(squares=[1,2]) + self.assertEqual(svg.count(' Date: Wed, 24 Jan 2024 04:07:03 +0530 Subject: [PATCH 08/23] fixed the background color of inaccuracy glypha again to match mistake but a bit darker --- chess/svg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chess/svg.py b/chess/svg.py index 2a19167ca..6acf88749 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -74,7 +74,7 @@ """, # "?!" "6": """ - + """ From 7e330b5003eea8c1e8e7aa35809a18d6cbbffd6e Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Wed, 24 Jan 2024 21:20:20 +0530 Subject: [PATCH 09/23] fix: bringing global constants in local scope for lil perf boost --- chess/svg.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chess/svg.py b/chess/svg.py index 6acf88749..6c34bbd1c 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -313,6 +313,8 @@ def board(board: Optional[chess.BaseBoard] = None, *, .. deprecated:: 1.1 Use *orientation* with a color instead of the *flipped* toggle. """ + # Bringing GLOBAL Constants in the local scope for a little performance boost + global SQUARE_SIZE, MARGIN, NAG_SIZE, PIECES, COORDS, NAGS, XX, CHECK_GRADIENT, DEFAULT_COLORS orientation ^= flipped inner_border = 1 if borders and coordinates else 0 outer_border = 1 if borders else 0 From 1f9977a6984ea1fb2451d510d03d3dfb443391fe Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Thu, 8 Feb 2024 01:03:35 +0530 Subject: [PATCH 10/23] chores: updates docs to show which nags are supported by nag param in chess.svg.board function --- chess/svg.py | 1 + 1 file changed, 1 insertion(+) diff --git a/chess/svg.py b/chess/svg.py index 6c34bbd1c..729300121 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -292,6 +292,7 @@ def board(board: Optional[chess.BaseBoard] = None, *, (if *coordinates* is enabled) the coordinate margin. :param style: A CSS stylesheet to include in the SVG image. :param nag: Pass ``NAG Constant`` to show Numerical Notation Glyphs (NAGs). + Supports !(great), !!(brilliant), ?(mistake), ?!(inaccuracy) and ??(blunder) (requires ``lastmove`` to be passed along as argument) >>> import chess From 33341e2a512faa26818a6b600e381a7345ab7c5d Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Sun, 12 May 2024 18:03:21 +0530 Subject: [PATCH 11/23] Removes border crap and adds coordinates inside the squares --- chess/svg.py | 117 ++++++++++++++++----------------------------------- 1 file changed, 36 insertions(+), 81 deletions(-) diff --git a/chess/svg.py b/chess/svg.py index 729300121..3f8f6ab3b 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -213,14 +213,7 @@ def _color(color: str) -> Tuple[str, float]: return color, 1.0 -def _coord(text: str, x: int, y: int, width: int, height: int, horizontal: bool, margin: int, *, color: str, opacity: float) -> ET.Element: - scale = margin / MARGIN - - if horizontal: - x += int(width - scale * width) // 2 - else: - y += int(height - scale * height) // 2 - +def _coord(text: str, x: float, y: float, scale: float, *, color: str, opacity: float) -> ET.Element: t = ET.Element("g", _attrs({ "transform": f"translate({x}, {y}) scale({scale}, {scale})", "fill": color, @@ -259,7 +252,6 @@ def board(board: Optional[chess.BaseBoard] = None, *, coordinates: bool = True, colors: Dict[str, str] = {}, flipped: bool = False, - borders: bool = False, style: Optional[str] = None, nag:Optional[int] = None) -> str: """ @@ -288,8 +280,6 @@ def board(board: Optional[chess.BaseBoard] = None, *, and ``arrow yellow``. Values should look like ``#ffce9e`` (opaque), or ``#15781B80`` (transparent). :param flipped: Pass ``True`` to flip the board. - :param borders: Pass ``True`` to enable a border around the board and, - (if *coordinates* is enabled) the coordinate margin. :param style: A CSS stylesheet to include in the SVG image. :param nag: Pass ``NAG Constant`` to show Numerical Notation Glyphs (NAGs). Supports !(great), !!(brilliant), ?(mistake), ?!(inaccuracy) and ??(blunder) @@ -317,10 +307,7 @@ def board(board: Optional[chess.BaseBoard] = None, *, # Bringing GLOBAL Constants in the local scope for a little performance boost global SQUARE_SIZE, MARGIN, NAG_SIZE, PIECES, COORDS, NAGS, XX, CHECK_GRADIENT, DEFAULT_COLORS orientation ^= flipped - inner_border = 1 if borders and coordinates else 0 - outer_border = 1 if borders else 0 - margin = 15 if coordinates else 0 - full_size = 2 * outer_border + 2 * margin + 2 * inner_border + 8 * SQUARE_SIZE + full_size = 8 * SQUARE_SIZE svg = _svg(full_size, size) desc = ET.SubElement(svg, "desc") defs = ET.SubElement(svg, "defs") @@ -328,65 +315,13 @@ def board(board: Optional[chess.BaseBoard] = None, *, if style: ET.SubElement(svg, "style").text = style - if outer_border: - outer_border_color, outer_border_opacity = _select_color(colors, "outer border") - ET.SubElement(svg, "rect", _attrs({ - "x": outer_border / 2, - "y": outer_border / 2, - "width": full_size - outer_border, - "height": full_size - outer_border, - "fill": "none", - "stroke": outer_border_color, - "stroke-width": outer_border, - "opacity": outer_border_opacity if outer_border_opacity < 1.0 else None, - })) - - if margin: - margin_color, margin_opacity = _select_color(colors, "margin") - ET.SubElement(svg, "rect", _attrs({ - "x": outer_border + margin / 2, - "y": outer_border + margin / 2, - "width": full_size - 2 * outer_border - margin, - "height": full_size - 2 * outer_border - margin, - "fill": "none", - "stroke": margin_color, - "stroke-width": margin, - "opacity": margin_opacity if margin_opacity < 1.0 else None, - })) - - if inner_border: - inner_border_color, inner_border_opacity = _select_color(colors, "inner border") - ET.SubElement(svg, "rect", _attrs({ - "x": outer_border + margin + inner_border / 2, - "y": outer_border + margin + inner_border / 2, - "width": full_size - 2 * outer_border - 2 * margin - inner_border, - "height": full_size - 2 * outer_border - 2 * margin - inner_border, - "fill": "none", - "stroke": inner_border_color, - "stroke-width": inner_border, - "opacity": inner_border_opacity if inner_border_opacity < 1.0 else None, - })) - - # Render coordinates. - if coordinates: - coord_color, coord_opacity = _select_color(colors, "coord") - for file_index, file_name in enumerate(chess.FILE_NAMES): - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + inner_border + margin + outer_border - # Keep some padding here to separate the ascender from the border - svg.append(_coord(file_name, x, 1, SQUARE_SIZE, margin, True, margin, color=coord_color, opacity=coord_opacity)) - svg.append(_coord(file_name, x, full_size - outer_border - margin, SQUARE_SIZE, margin, True, margin, color=coord_color, opacity=coord_opacity)) - for rank_index, rank_name in enumerate(chess.RANK_NAMES): - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + inner_border + margin + outer_border - svg.append(_coord(rank_name, 0, y, margin, SQUARE_SIZE, False, margin, color=coord_color, opacity=coord_opacity)) - svg.append(_coord(rank_name, full_size - outer_border - margin, y, margin, SQUARE_SIZE, False, margin, color=coord_color, opacity=coord_opacity)) - # Render board. for square, bb in enumerate(chess.BB_SQUARES): file_index = chess.square_file(square) rank_index = chess.square_rank(square) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + inner_border + margin + outer_border - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + inner_border + margin + outer_border + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE cls = ["square", "light" if chess.BB_LIGHT_SQUARES & bb else "dark"] if lastmove and square in [lastmove.from_square, lastmove.to_square]: @@ -427,8 +362,8 @@ def board(board: Optional[chess.BaseBoard] = None, *, file_index = chess.square_file(check) rank_index = chess.square_rank(check) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + margin + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE ET.SubElement(svg, "rect", _attrs({ "x": x, @@ -453,8 +388,8 @@ def board(board: Optional[chess.BaseBoard] = None, *, file_index = chess.square_file(square) rank_index = chess.square_rank(square) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + margin + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE piece = board.piece_at(square) if piece: @@ -472,8 +407,8 @@ def board(board: Optional[chess.BaseBoard] = None, *, for square in squares: file_index = chess.square_file(square) rank_index = chess.square_rank(square) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + margin + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE # Render selected squares ET.SubElement(svg, "use", _attrs({ "href": "#xx", @@ -500,10 +435,10 @@ def board(board: Optional[chess.BaseBoard] = None, *, head_file = chess.square_file(head) head_rank = chess.square_rank(head) - xtail = outer_border + margin + inner_border + (tail_file + 0.5 if orientation else 7.5 - tail_file) * SQUARE_SIZE - ytail = outer_border + margin + inner_border + (7.5 - tail_rank if orientation else tail_rank + 0.5) * SQUARE_SIZE - xhead = outer_border + margin + inner_border + (head_file + 0.5 if orientation else 7.5 - head_file) * SQUARE_SIZE - yhead = outer_border + margin + inner_border + (7.5 - head_rank if orientation else head_rank + 0.5) * SQUARE_SIZE + xtail = (tail_file + 0.5 if orientation else 7.5 - tail_file) * SQUARE_SIZE + ytail = (7.5 - tail_rank if orientation else tail_rank + 0.5) * SQUARE_SIZE + xhead = (head_file + 0.5 if orientation else 7.5 - head_file) * SQUARE_SIZE + yhead = (7.5 - head_rank if orientation else head_rank + 0.5) * SQUARE_SIZE if (head_file, head_rank) == (tail_file, tail_rank): ET.SubElement(svg, "circle", _attrs({ @@ -562,8 +497,8 @@ def board(board: Optional[chess.BaseBoard] = None, *, id = ele.attrib.get("id") file_index = chess.square_file(lastmove.to_square) rank_index = chess.square_rank(lastmove.to_square) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + margin + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE from_file_index = chess.square_file(lastmove.from_square) # Making sure the NAGs doesn't overlap the Last Move Arrow by switching # between top left and top right corner depending upon where the Arrow @@ -576,4 +511,24 @@ def board(board: Optional[chess.BaseBoard] = None, *, "y": y, })) + # Render coordinates. + if coordinates: + light_color, light_opacity = _select_color(colors, "square light") + dark_color, dark_opacity = _select_color(colors, "square dark") + text_scale = 0.5 + width = 18 + height = 18 + width *= text_scale + height *= text_scale + for file_index, file_name in enumerate(chess.FILE_NAMES): + x = ((file_index if orientation else 7 - file_index) * SQUARE_SIZE) - width + y = full_size - height + coord_color, coord_opacity = (light_color, light_opacity) if (file_index+orientation)%2 == 1 else (dark_color, dark_opacity) + svg.append(_coord(file_name, x+1.5, y-1, text_scale, color=coord_color, opacity=coord_opacity)) + x += (7 - file_index if orientation else file_index) * SQUARE_SIZE + x += SQUARE_SIZE + for rank_index, rank_name in enumerate(chess.RANK_NAMES): + y = ((7 - rank_index if orientation else rank_index) * SQUARE_SIZE) - height + coord_color, coord_opacity = (dark_color, dark_opacity) if (rank_index+orientation)%2 == 1 else (light_color, light_opacity) + svg.append(_coord(rank_name, x-1, y+3, text_scale, color=coord_color, opacity=coord_opacity)) return SvgWrapper(ET.tostring(svg).decode("utf-8")) From d8b7160b0ca2931a090d0abdba2d14b31c9d6ba5 Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Sun, 12 May 2024 18:06:50 +0530 Subject: [PATCH 12/23] Fixed the z-order of coords so that they don't overlap glyphs or other important stuff. --- chess/svg.py | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/chess/svg.py b/chess/svg.py index 3f8f6ab3b..7ade5320a 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -400,6 +400,27 @@ def board(board: Optional[chess.BaseBoard] = None, *, "transform": f"translate({x:d}, {y:d})", }) + # Render coordinates. + if coordinates: + light_color, light_opacity = _select_color(colors, "square light") + dark_color, dark_opacity = _select_color(colors, "square dark") + text_scale = 0.5 + width = 18 + height = 18 + width *= text_scale + height *= text_scale + for file_index, file_name in enumerate(chess.FILE_NAMES): + x = ((file_index if orientation else 7 - file_index) * SQUARE_SIZE) - width + y = full_size - height + coord_color, coord_opacity = (light_color, light_opacity) if (file_index+orientation)%2 == 1 else (dark_color, dark_opacity) + svg.append(_coord(file_name, x+1.5, y-1, text_scale, color=coord_color, opacity=coord_opacity)) + x += (7 - file_index if orientation else file_index) * SQUARE_SIZE + x += SQUARE_SIZE + for rank_index, rank_name in enumerate(chess.RANK_NAMES): + y = ((7 - rank_index if orientation else rank_index) * SQUARE_SIZE) - height + coord_color, coord_opacity = (dark_color, dark_opacity) if (rank_index+orientation)%2 == 1 else (light_color, light_opacity) + svg.append(_coord(rank_name, x-1, y+3, text_scale, color=coord_color, opacity=coord_opacity)) + # Render X Squares if squares is not None: defs.append(ET.fromstring(XX)) @@ -510,25 +531,5 @@ def board(board: Optional[chess.BaseBoard] = None, *, "x": x, "y": y, })) - - # Render coordinates. - if coordinates: - light_color, light_opacity = _select_color(colors, "square light") - dark_color, dark_opacity = _select_color(colors, "square dark") - text_scale = 0.5 - width = 18 - height = 18 - width *= text_scale - height *= text_scale - for file_index, file_name in enumerate(chess.FILE_NAMES): - x = ((file_index if orientation else 7 - file_index) * SQUARE_SIZE) - width - y = full_size - height - coord_color, coord_opacity = (light_color, light_opacity) if (file_index+orientation)%2 == 1 else (dark_color, dark_opacity) - svg.append(_coord(file_name, x+1.5, y-1, text_scale, color=coord_color, opacity=coord_opacity)) - x += (7 - file_index if orientation else file_index) * SQUARE_SIZE - x += SQUARE_SIZE - for rank_index, rank_name in enumerate(chess.RANK_NAMES): - y = ((7 - rank_index if orientation else rank_index) * SQUARE_SIZE) - height - coord_color, coord_opacity = (dark_color, dark_opacity) if (rank_index+orientation)%2 == 1 else (light_color, light_opacity) - svg.append(_coord(rank_name, x-1, y+3, text_scale, color=coord_color, opacity=coord_opacity)) + return SvgWrapper(ET.tostring(svg).decode("utf-8")) From 8d808ce3dd51772a3c568bdf8a18228636daaa43 Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Sun, 12 May 2024 18:39:04 +0530 Subject: [PATCH 13/23] Fixed type errors --- .gitignore | 1 + chess/svg.py | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 076e4fcff..2dca5d95c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ data/syzygy/giveaway/*.[gs]tb[wz] fuzz/corpus release-v*.txt +.venv \ No newline at end of file diff --git a/chess/svg.py b/chess/svg.py index 7ade5320a..450185121 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -405,19 +405,18 @@ def board(board: Optional[chess.BaseBoard] = None, *, light_color, light_opacity = _select_color(colors, "square light") dark_color, dark_opacity = _select_color(colors, "square dark") text_scale = 0.5 - width = 18 - height = 18 - width *= text_scale - height *= text_scale + coord_size = 18 + width = coord_size * text_scale + height = coord_size * text_scale for file_index, file_name in enumerate(chess.FILE_NAMES): - x = ((file_index if orientation else 7 - file_index) * SQUARE_SIZE) - width - y = full_size - height + x = ((file_index if orientation else 7 - file_index) * SQUARE_SIZE) - width # type: ignore + y = full_size - height # type: ignore coord_color, coord_opacity = (light_color, light_opacity) if (file_index+orientation)%2 == 1 else (dark_color, dark_opacity) svg.append(_coord(file_name, x+1.5, y-1, text_scale, color=coord_color, opacity=coord_opacity)) x += (7 - file_index if orientation else file_index) * SQUARE_SIZE x += SQUARE_SIZE for rank_index, rank_name in enumerate(chess.RANK_NAMES): - y = ((7 - rank_index if orientation else rank_index) * SQUARE_SIZE) - height + y = ((7 - rank_index if orientation else rank_index) * SQUARE_SIZE) - height # type: ignore coord_color, coord_opacity = (dark_color, dark_opacity) if (rank_index+orientation)%2 == 1 else (light_color, light_opacity) svg.append(_coord(rank_name, x-1, y+3, text_scale, color=coord_color, opacity=coord_opacity)) From 88171b7b9c6d2a4c94df4d7919aea0e9eb4cd8ac Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Mon, 13 May 2024 00:13:33 +0530 Subject: [PATCH 14/23] Fixed the bug where glyphs weren't placed correctly in flipped board --- chess/svg.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/chess/svg.py b/chess/svg.py index 450185121..6addc25ef 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -519,10 +519,12 @@ def board(board: Optional[chess.BaseBoard] = None, *, rank_index = chess.square_rank(lastmove.to_square) x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE - from_file_index = chess.square_file(lastmove.from_square) # Making sure the NAGs doesn't overlap the Last Move Arrow by switching # between top left and top right corner depending upon where the Arrow # is coming from. + from_file_index = chess.square_file(lastmove.from_square) + file_index = (file_index if orientation else 7 - file_index) + from_file_index = (from_file_index if orientation else 7 - from_file_index) x = x + (SQUARE_SIZE - NAG_SIZE if file_index >= from_file_index else 0) ET.SubElement(svg, "use", _attrs({ "href": f"#{id}", From f86269d1f811d97384f7a51ab9afc8ce0a9d2f9e Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Sun, 26 May 2024 03:44:28 +0530 Subject: [PATCH 15/23] Decouple the rendering of lastmove rects from rendering of board for performance --- chess/svg.py | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/chess/svg.py b/chess/svg.py index 6addc25ef..bc77faa42 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -90,8 +90,6 @@ "square dark lastmove": "#aaa23b", "square light lastmove": "#cdd16a", "margin": "#212121", - "inner border": "#111", - "outer border": "#111", "coord": "#e5e5e5", "arrow green": "#15781B80", "arrow red": "#88202080", @@ -324,8 +322,6 @@ def board(board: Optional[chess.BaseBoard] = None, *, y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE cls = ["square", "light" if chess.BB_LIGHT_SQUARES & bb else "dark"] - if lastmove and square in [lastmove.from_square, lastmove.to_square]: - cls.append("lastmove") square_color, square_opacity = _select_color(colors, " ".join(cls)) cls.append(chess.SQUARE_NAMES[square]) @@ -355,6 +351,32 @@ def board(board: Optional[chess.BaseBoard] = None, *, "fill": fill_color, "opacity": fill_opacity if fill_opacity < 1.0 else None, })) + + # Rendering lastmove + if lastmove: + for square in [lastmove.from_square, lastmove.to_square]: + bb = 1 << square + file_index = chess.square_file(square) + rank_index = chess.square_rank(square) + + x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + + cls = ["square", "light" if chess.BB_LIGHT_SQUARES & bb else "dark", "lastmove"] + square_color, square_opacity = _select_color(colors, " ".join(cls)) + + cls.append(chess.SQUARE_NAMES[square]) + + ET.SubElement(svg, "rect", _attrs({ + "x": x, + "y": y, + "width": SQUARE_SIZE, + "height": SQUARE_SIZE, + "class": " ".join(cls), + "stroke": "none", + "fill": square_color, + "opacity": square_opacity if square_opacity < 1.0 else None, + })) # Render check mark. if check is not None: From 5eba38f8b96096e7aa4678c63ca7f8044d7bc712 Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Wed, 27 Nov 2024 04:05:31 +0530 Subject: [PATCH 16/23] feat: adds parser and svg print support for Novelty nag --- chess/pgn.py | 4 ++++ chess/svg.py | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/chess/pgn.py b/chess/pgn.py index 55eddbc29..bf0008cb5 100644 --- a/chess/pgn.py +++ b/chess/pgn.py @@ -92,7 +92,9 @@ |(\() |(\)) |(\*|1-0|0-1|1/2-1/2) + |(\!N) |([\?!]{1,2}) + |(TN) """, re.DOTALL | re.VERBOSE) SKIP_MOVETEXT_REGEX = re.compile(r""";|\{|\}""") @@ -1736,6 +1738,8 @@ def read_game(handle: TextIO, *, Visitor: Any = GameBuilder) -> Any: visitor.visit_nag(NAG_SPECULATIVE_MOVE) elif token == "?!": visitor.visit_nag(NAG_DUBIOUS_MOVE) + elif token == "TN" or token == "!N": + visitor.visit_nag(NAG_NOVELTY) elif token in ["1-0", "0-1", "1/2-1/2", "*"] and len(board_stack) == 1: visitor.visit_result(token) else: diff --git a/chess/svg.py b/chess/svg.py index bc77faa42..f646d5c0e 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -77,7 +77,13 @@ - """ + """, + # "N" + "146": """ + + + """ + } XX = """""" # noqa: E501 From 5809ef1e498dc2a4025318786a629acff1fc4fcb Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Wed, 27 Nov 2024 04:07:48 +0530 Subject: [PATCH 17/23] fix: taken care all the linter warnings --- chess/__init__.py | 13 +++++++------ chess/pgn.py | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/chess/__init__.py b/chess/__init__.py index a9328a04b..64c2b7d29 100644 --- a/chess/__init__.py +++ b/chess/__init__.py @@ -1340,7 +1340,7 @@ def transform(self: BaseBoardT, f: Callable[[Bitboard], Bitboard]) -> BaseBoardT board.apply_transform(f) return board - def apply_mirror(self: BaseBoardT) -> None: + def apply_mirror(self: BaseBoard) -> None: self.apply_transform(flip_vertical) self.occupied_co[WHITE], self.occupied_co[BLACK] = self.occupied_co[BLACK], self.occupied_co[WHITE] @@ -1561,14 +1561,14 @@ class Board(BaseBoard): manipulation. """ - def __init__(self: BoardT, fen: Optional[str] = STARTING_FEN, *, chess960: bool = False) -> None: + def __init__(self: Board, fen: Optional[str] = STARTING_FEN, *, chess960: bool = False) -> None: BaseBoard.__init__(self, None) self.chess960 = chess960 self.ep_square = None self.move_stack = [] - self._stack: List[_BoardState[BoardT]] = [] + self._stack: List[_BoardState[Board]] = [] if fen is None: self.clear() @@ -2177,7 +2177,7 @@ def _board_state(self: BoardT) -> _BoardState[BoardT]: def _push_capture(self, move: Move, capture_square: Square, piece_type: PieceType, was_promoted: bool) -> None: pass - def push(self: BoardT, move: Move) -> None: + def push(self: Board, move: Move) -> None: """ Updates the position with the given *move* and puts it onto the move stack. @@ -2262,6 +2262,7 @@ def push(self: BoardT, move: Move) -> None: elif diff == -16 and square_rank(move.from_square) == 6: self.ep_square = move.from_square - 8 elif move.to_square == ep_square and abs(diff) in [7, 9] and not captured_piece_type: + assert ep_square is not None # Remove pawns captured en passant. down = -8 if self.turn == WHITE else 8 capture_square = ep_square + down @@ -2298,7 +2299,7 @@ def push(self: BoardT, move: Move) -> None: # Swap turn. self.turn = not self.turn - def pop(self: BoardT) -> Move: + def pop(self: Board) -> Move: """ Restores the previous position and returns the last move from the stack. @@ -3696,7 +3697,7 @@ def transform(self: BoardT, f: Callable[[Bitboard], Bitboard]) -> BoardT: board.apply_transform(f) return board - def apply_mirror(self: BoardT) -> None: + def apply_mirror(self: Board) -> None: super().apply_mirror() self.turn = not self.turn diff --git a/chess/pgn.py b/chess/pgn.py index bf0008cb5..c1969301e 100644 --- a/chess/pgn.py +++ b/chess/pgn.py @@ -924,7 +924,7 @@ def without_tag_roster(cls: Type[GameT]) -> GameT: return cls(headers={}) @classmethod - def builder(cls: Type[GameT]) -> GameBuilder[GameT]: + def builder(cls: Type[GameT]) -> GameBuilder[Game]: return GameBuilder(Game=cls) def __repr__(self) -> str: @@ -1028,7 +1028,7 @@ def __repr__(self) -> str: ", ".join("{}={!r}".format(key, value) for key, value in self.items())) @classmethod - def builder(cls: Type[HeadersT]) -> HeadersBuilder[HeadersT]: + def builder(cls: Type[HeadersT]) -> HeadersBuilder[Headers]: return HeadersBuilder(Headers=cls) @@ -1182,7 +1182,7 @@ class GameBuilder(BaseVisitor[GameT]): @typing.overload def __init__(self: GameBuilder[Game]) -> None: ... @typing.overload - def __init__(self: GameBuilder[GameT], *, Game: Type[GameT]) -> None: ... + def __init__(self: GameBuilder[Game], *, Game: Type[GameT]) -> None: ... def __init__(self, *, Game: Any = Game) -> None: self.Game = Game @@ -1279,7 +1279,7 @@ class HeadersBuilder(BaseVisitor[HeadersT]): @typing.overload def __init__(self: HeadersBuilder[Headers]) -> None: ... @typing.overload - def __init__(self: HeadersBuilder[HeadersT], *, Headers: Type[Headers]) -> None: ... + def __init__(self: HeadersBuilder[Headers], *, Headers: Type[Headers]) -> None: ... def __init__(self, *, Headers: Any = Headers) -> None: self.Headers = Headers From 98a1a38b7fe5c2eec2cb255da9d0c5e5b67143c9 Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Thu, 28 Nov 2024 04:02:47 +0530 Subject: [PATCH 18/23] fix: Updated Novelty nag's color and reduced the size of letter N --- chess/svg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chess/svg.py b/chess/svg.py index f646d5c0e..474c6bfac 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -80,8 +80,8 @@ """, # "N" "146": """ - - + + """ } From bf7f63238df17b2559884ce3c44f267a9f56bce1 Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Thu, 28 Nov 2024 04:04:18 +0530 Subject: [PATCH 19/23] fixes placement of the nag, made it more dynamic and also respects the first/last ranks and files --- chess/svg.py | 95 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 56 insertions(+), 39 deletions(-) diff --git a/chess/svg.py b/chess/svg.py index 474c6bfac..b45b40cca 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -308,8 +308,6 @@ def board(board: Optional[chess.BaseBoard] = None, *, .. deprecated:: 1.1 Use *orientation* with a color instead of the *flipped* toggle. """ - # Bringing GLOBAL Constants in the local scope for a little performance boost - global SQUARE_SIZE, MARGIN, NAG_SIZE, PIECES, COORDS, NAGS, XX, CHECK_GRADIENT, DEFAULT_COLORS orientation ^= flipped full_size = 8 * SQUARE_SIZE svg = _svg(full_size, size) @@ -321,11 +319,11 @@ def board(board: Optional[chess.BaseBoard] = None, *, # Render board. for square, bb in enumerate(chess.BB_SQUARES): - file_index = chess.square_file(square) - rank_index = chess.square_rank(square) + to_file = chess.square_file(square) + to_rank = chess.square_rank(square) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + x = (to_file if orientation else 7 - to_file) * SQUARE_SIZE + y = (7 - to_rank if orientation else to_rank) * SQUARE_SIZE cls = ["square", "light" if chess.BB_LIGHT_SQUARES & bb else "dark"] square_color, square_opacity = _select_color(colors, " ".join(cls)) @@ -362,11 +360,11 @@ def board(board: Optional[chess.BaseBoard] = None, *, if lastmove: for square in [lastmove.from_square, lastmove.to_square]: bb = 1 << square - file_index = chess.square_file(square) - rank_index = chess.square_rank(square) + to_file = chess.square_file(square) + to_rank = chess.square_rank(square) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + x = (to_file if orientation else 7 - to_file) * SQUARE_SIZE + y = (7 - to_rank if orientation else to_rank) * SQUARE_SIZE cls = ["square", "light" if chess.BB_LIGHT_SQUARES & bb else "dark", "lastmove"] square_color, square_opacity = _select_color(colors, " ".join(cls)) @@ -387,11 +385,11 @@ def board(board: Optional[chess.BaseBoard] = None, *, # Render check mark. if check is not None: defs.append(ET.fromstring(CHECK_GRADIENT)) - file_index = chess.square_file(check) - rank_index = chess.square_rank(check) + to_file = chess.square_file(check) + to_rank = chess.square_rank(check) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + x = (to_file if orientation else 7 - to_file) * SQUARE_SIZE + y = (7 - to_rank if orientation else to_rank) * SQUARE_SIZE ET.SubElement(svg, "rect", _attrs({ "x": x, @@ -413,11 +411,11 @@ def board(board: Optional[chess.BaseBoard] = None, *, defs.append(ET.fromstring(PIECES[chess.Piece(piece_type, piece_color).symbol()])) # Rendering pieces for square, bb in enumerate(chess.BB_SQUARES): - file_index = chess.square_file(square) - rank_index = chess.square_rank(square) + to_file = chess.square_file(square) + to_rank = chess.square_rank(square) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + x = (to_file if orientation else 7 - to_file) * SQUARE_SIZE + y = (7 - to_rank if orientation else to_rank) * SQUARE_SIZE piece = board.piece_at(square) if piece: @@ -436,16 +434,16 @@ def board(board: Optional[chess.BaseBoard] = None, *, coord_size = 18 width = coord_size * text_scale height = coord_size * text_scale - for file_index, file_name in enumerate(chess.FILE_NAMES): - x = ((file_index if orientation else 7 - file_index) * SQUARE_SIZE) - width # type: ignore + for to_file, file_name in enumerate(chess.FILE_NAMES): + x = ((to_file if orientation else 7 - to_file) * SQUARE_SIZE) - width # type: ignore y = full_size - height # type: ignore - coord_color, coord_opacity = (light_color, light_opacity) if (file_index+orientation)%2 == 1 else (dark_color, dark_opacity) + coord_color, coord_opacity = (light_color, light_opacity) if (to_file+orientation)%2 == 1 else (dark_color, dark_opacity) svg.append(_coord(file_name, x+1.5, y-1, text_scale, color=coord_color, opacity=coord_opacity)) - x += (7 - file_index if orientation else file_index) * SQUARE_SIZE + x += (7 - to_file if orientation else to_file) * SQUARE_SIZE x += SQUARE_SIZE - for rank_index, rank_name in enumerate(chess.RANK_NAMES): - y = ((7 - rank_index if orientation else rank_index) * SQUARE_SIZE) - height # type: ignore - coord_color, coord_opacity = (dark_color, dark_opacity) if (rank_index+orientation)%2 == 1 else (light_color, light_opacity) + for to_rank, rank_name in enumerate(chess.RANK_NAMES): + y = ((7 - to_rank if orientation else to_rank) * SQUARE_SIZE) - height # type: ignore + coord_color, coord_opacity = (dark_color, dark_opacity) if (to_rank+orientation)%2 == 1 else (light_color, light_opacity) svg.append(_coord(rank_name, x-1, y+3, text_scale, color=coord_color, opacity=coord_opacity)) # Render X Squares @@ -453,10 +451,10 @@ def board(board: Optional[chess.BaseBoard] = None, *, defs.append(ET.fromstring(XX)) squares = chess.SquareSet(squares) if squares else chess.SquareSet() for square in squares: - file_index = chess.square_file(square) - rank_index = chess.square_rank(square) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + to_file = chess.square_file(square) + to_rank = chess.square_rank(square) + x = (to_file if orientation else 7 - to_file) * SQUARE_SIZE + y = (7 - to_rank if orientation else to_rank) * SQUARE_SIZE # Render selected squares ET.SubElement(svg, "use", _attrs({ "href": "#xx", @@ -543,17 +541,36 @@ def board(board: Optional[chess.BaseBoard] = None, *, ele = ET.fromstring(NAGS[str(nag)]) defs.append(ele) id = ele.attrib.get("id") - file_index = chess.square_file(lastmove.to_square) - rank_index = chess.square_rank(lastmove.to_square) - x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE - y = (7 - rank_index if orientation else rank_index) * SQUARE_SIZE + to_file = chess.square_file(lastmove.to_square) + to_rank = chess.square_rank(lastmove.to_square) + to_file = to_file if orientation else 7 - to_file + to_rank = 7 - to_rank if orientation else to_rank + x = to_file * SQUARE_SIZE + y = to_rank * SQUARE_SIZE + + from_file = chess.square_file(lastmove.from_square) + from_rank = chess.square_rank(lastmove.from_square) + from_file = from_file if orientation else 7 - from_file + from_rank = 7 - from_rank if orientation else from_rank + + delta_file = to_file - from_file + offset = SQUARE_SIZE - NAG_SIZE + corner_offset = NAG_SIZE/2 + # Making sure the NAGs doesn't overlap the Last Move Arrow by switching - # between top left and top right corner depending upon where the Arrow - # is coming from. - from_file_index = chess.square_file(lastmove.from_square) - file_index = (file_index if orientation else 7 - file_index) - from_file_index = (from_file_index if orientation else 7 - from_file_index) - x = x + (SQUARE_SIZE - NAG_SIZE if file_index >= from_file_index else 0) + # between appropriate corners depending upon where the Arrow is coming from. + if delta_file >= 0: # Moving towards the right + x += offset # Top-right corner + x += corner_offset + if to_file == 7: + x -= corner_offset + else: # Moving towards the left OR Same File + x -= corner_offset + if to_file == 0: + x += corner_offset + y -= corner_offset + if to_rank == 0: + y += corner_offset ET.SubElement(svg, "use", _attrs({ "href": f"#{id}", "xlink:href": f"#{id}", From c70fe859fedd2a1b1cebb5afea18beeeea7b1970 Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Fri, 29 Nov 2024 01:36:42 +0530 Subject: [PATCH 20/23] Reduces size of arrow --- chess/svg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chess/svg.py b/chess/svg.py index b45b40cca..c3072cd9d 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -498,7 +498,7 @@ def board(board: Optional[chess.BaseBoard] = None, *, "class": "circle", })) else: - marker_size = 0.75 * SQUARE_SIZE + marker_size = 0.50 * SQUARE_SIZE marker_margin = 0.1 * SQUARE_SIZE dx, dy = xhead - xtail, yhead - ytail @@ -517,7 +517,7 @@ def board(board: Optional[chess.BaseBoard] = None, *, "y2": shaft_y, "stroke": color, "opacity": opacity if opacity < 1.0 else None, - "stroke-width": SQUARE_SIZE * 0.2, + "stroke-width": SQUARE_SIZE * 0.15, "stroke-linecap": "butt", "class": "arrow", })) From 473d6573f95a5e162325d3f692247405111e62d7 Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Fri, 29 Nov 2024 03:59:02 +0530 Subject: [PATCH 21/23] bumped size of nags and fix their colors --- chess/svg.py | 74 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 18 deletions(-) diff --git a/chess/svg.py b/chess/svg.py index c3072cd9d..98fada4d8 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -50,39 +50,77 @@ NAGS = { # "!" "1": """ - - + + + + + + + + """, # "?" "2": """ - - + + + + + + + + + + """, # "!!" "3": """ - - - + + + + + + + + + + """, # "??" "4": """ - - - - - + + + + + + + + + + + + + + """, # "?!" "6": """ - - - + + + + + + + + + + """, # "N" "146": """ - - - """ + + + + """ } From 97eac2c6d4269909705f0e6b8b88d6ac3ca94afd Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Mon, 16 Dec 2024 21:52:53 +0530 Subject: [PATCH 22/23] fixed nag color of inaccuracy and mistake --- chess/svg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chess/svg.py b/chess/svg.py index 98fada4d8..475b1957c 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -63,7 +63,7 @@ "2": """ - + @@ -105,7 +105,7 @@ # "?!" "6": """ - + From 1aff9319a3049ffe6a16545de674a6d9fdc187b5 Mon Sep 17 00:00:00 2001 From: Vikas Gautam Date: Wed, 29 Jan 2025 04:08:32 +0530 Subject: [PATCH 23/23] fix: updated the inaccuracy nag color to be same as mistake --- chess/svg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chess/svg.py b/chess/svg.py index 475b1957c..af609dcfc 100644 --- a/chess/svg.py +++ b/chess/svg.py @@ -105,7 +105,7 @@ # "?!" "6": """ - +