Skip to content

Commit 193a3ba

Browse files
committed
Fill squares with arbitrary colors (closes #774)
1 parent 6451f52 commit 193a3ba

2 files changed

Lines changed: 47 additions & 12 deletions

File tree

chess/svg.py

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,11 @@ def _attrs(attrs: Dict[str, Union[str, int, float, None]]) -> Dict[str, str]:
183183
return {k: str(v) for k, v in attrs.items() if v is not None}
184184

185185

186-
def _color(colors: Dict[str, str], color: str) -> Tuple[str, float]:
187-
color = colors.get(color, DEFAULT_COLORS[color])
186+
def _select_color(colors: Dict[str, str], color: str) -> Tuple[str, float]:
187+
return _color(colors.get(color, DEFAULT_COLORS[color]))
188+
189+
190+
def _color(color: str) -> Tuple[str, float]:
188191
if color.startswith("#"):
189192
try:
190193
if len(color) == 5:
@@ -236,6 +239,7 @@ def board(board: Optional[chess.BaseBoard] = None, *,
236239
lastmove: Optional[chess.Move] = None,
237240
check: Optional[Square] = None,
238241
arrows: Iterable[Union[Arrow, Tuple[Square, Square]]] = [],
242+
fill: Dict[Square, str] = {},
239243
squares: Optional[IntoSquareSet] = None,
240244
size: Optional[int] = None,
241245
coordinates: bool = True,
@@ -254,7 +258,10 @@ def board(board: Optional[chess.BaseBoard] = None, *,
254258
``[chess.svg.Arrow(chess.E2, chess.E4)]``, or a list of tuples, like
255259
``[(chess.E2, chess.E4)]``. An arrow from a square pointing to the same
256260
square is drawn as a circle, like ``[(chess.E2, chess.E2)]``.
257-
:param squares: A :class:`chess.SquareSet` with selected squares.
261+
:param fill: A dictionary mapping squares to a colors that they should be
262+
filled with.
263+
:param squares: A :class:`chess.SquareSet` with selected squares to mark
264+
with an X.
258265
:param size: The size of the image in pixels (e.g., ``400`` for a 400 by
259266
400 board), or ``None`` (the default) for no size limit.
260267
:param coordinates: Pass ``False`` to disable the coordinate margin.
@@ -270,8 +277,14 @@ def board(board: Optional[chess.BaseBoard] = None, *,
270277
>>> import chess.svg
271278
>>>
272279
>>> board = chess.Board("8/8/8/8/4N3/8/8/8 w - - 0 1")
273-
>>> squares = board.attacks(chess.E4)
274-
>>> chess.svg.board(board, squares=squares, size=350) # doctest: +SKIP
280+
>>>
281+
>>> chess.svg.board(
282+
... board,
283+
... fill=dict.fromkeys(board.attacks(chess.E4), "#cc0000cc") | {chess.E4: "#00cc00cc"},
284+
... arrows=[chess.svg.Arrow(chess.E4, chess.F6, color="#0000cccc")],
285+
... squares=chess.SquareSet(chess.BB_DARK_SQUARES & chess.BB_FILE_B),
286+
... size=350,
287+
... ) # doctest: +SKIP
275288
276289
.. image:: ../docs/Ne4.svg
277290
:alt: 8/8/8/8/4N3/8/8/8
@@ -307,7 +320,7 @@ def board(board: Optional[chess.BaseBoard] = None, *,
307320

308321
# Render coordinates.
309322
if coordinates:
310-
margin_color, margin_opacity = _color(colors, "margin")
323+
margin_color, margin_opacity = _select_color(colors, "margin")
311324
ET.SubElement(svg, "rect", _attrs({
312325
"x": 0,
313326
"y": 0,
@@ -316,7 +329,7 @@ def board(board: Optional[chess.BaseBoard] = None, *,
316329
"fill": margin_color,
317330
"opacity": margin_opacity if margin_opacity < 1.0 else None,
318331
}))
319-
coord_color, coord_opacity = _color(colors, "coord")
332+
coord_color, coord_opacity = _select_color(colors, "coord")
320333
for file_index, file_name in enumerate(chess.FILE_NAMES):
321334
x = (file_index if orientation else 7 - file_index) * SQUARE_SIZE + margin
322335
svg.append(_coord(file_name, x, 0, SQUARE_SIZE, margin, True, margin, color=coord_color, opacity=coord_opacity))
@@ -337,7 +350,7 @@ def board(board: Optional[chess.BaseBoard] = None, *,
337350
cls = ["square", "light" if chess.BB_LIGHT_SQUARES & bb else "dark"]
338351
if lastmove and square in [lastmove.from_square, lastmove.to_square]:
339352
cls.append("lastmove")
340-
fill_color, fill_opacity = _color(colors, " ".join(cls))
353+
square_color, square_opacity = _select_color(colors, " ".join(cls))
341354

342355
cls.append(chess.SQUARE_NAMES[square])
343356

@@ -348,10 +361,25 @@ def board(board: Optional[chess.BaseBoard] = None, *,
348361
"height": SQUARE_SIZE,
349362
"class": " ".join(cls),
350363
"stroke": "none",
351-
"fill": fill_color,
352-
"opacity": fill_opacity if fill_opacity < 1.0 else None,
364+
"fill": square_color,
365+
"opacity": square_opacity if square_opacity < 1.0 else None,
353366
}))
354367

368+
try:
369+
fill_color, fill_opacity = _color(fill[square])
370+
except KeyError:
371+
pass
372+
else:
373+
ET.SubElement(svg, "rect", _attrs({
374+
"x": x,
375+
"y": y,
376+
"width": SQUARE_SIZE,
377+
"height": SQUARE_SIZE,
378+
"stroke": "none",
379+
"fill": fill_color,
380+
"opacity": fill_opacity if fill_opacity < 1.0 else None,
381+
}))
382+
355383
# Render check mark.
356384
if check is not None:
357385
file_index = chess.square_file(check)
@@ -405,7 +433,7 @@ def board(board: Optional[chess.BaseBoard] = None, *,
405433
color = "green"
406434

407435
try:
408-
color, opacity = _color(colors, " ".join(["arrow", color]))
436+
color, opacity = _select_color(colors, " ".join(["arrow", color]))
409437
except KeyError:
410438
opacity = 1.0
411439

0 commit comments

Comments
 (0)