@@ -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