diff --git a/chess/html.py b/chess/html.py new file mode 100644 index 000000000..c3d99f551 --- /dev/null +++ b/chess/html.py @@ -0,0 +1,218 @@ +# -*- coding: utf-8 -*- +# This file is an addition to the python-chess library. +# Copyright (C) 2018 John Jackson +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +"""A module for python-chess which outputs an HTML table of a board.""" + +import chess + +HTML_HEAD = """ + + + python-chess output + + + +""" +DEFAULT_STYLE = """.chess_board { + font-size:4em; + font-family: sans-serif; + border-collapse: collapse; + border-spacing: 0; +} +.chess_board .piece { + color:#000; + display:block; + height:1.25em; + width:1.25em; + position:relative; + text-decoration:none; +} +.chess_board td { + background: rgba(209, 139, 71, 1); + height:1.25em; + width:1.25em; + padding: 0; + text-align:center; + vertical-align:middle; +} +.chess_board td.lastmove { + background: #aaa23b; +} +.chess_board tr:nth-child(odd) td:nth-child(even), +.chess_board tr:nth-child(even) td:nth-child(odd) { + background:rgba(255, 206, 158, 1); +} +.chess_board tr:nth-child(odd) td:nth-child(even).lastmove, +.chess_board tr:nth-child(even) td:nth-child(odd).lastmove { + background: #cdd16a; +} +.chess_board th { + background: transparent; + height:2.5em; + width:2.5em; + padding: 0; + text-align:center; + vertical-align:middle; + font-weight: normal; + font-size:.5em; +} +.chess_board .check { + background: -moz-radial-gradient(center, ellipse cover, rgba(255,0,0,1) 0%, rgba(231,0,0,0.5) 50%, rgba(158,0,0,0) 100%); + background: -webkit-radial-gradient(center, ellipse cover, rgba(255,0,0,1) 0%,rgba(231,0,0,0.5) 50%,rgba(158,0,0,0) 100%); + background: radial-gradient(ellipse at center, rgba(255,0,0,1) 0%,rgba(231,0,0,0.5) 50%,rgba(158,0,0,0) 100%); + filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ff0000', endColorstr='#009e0000',GradientType=1 ); +} +.chess_board .attack { + position: absolute; + z-index: 999; + display: block; + width: 1.25em; + height: 1.25em; +} +""" +TABLE_START = """ +""" +TABLE_HEAD_COORDS = """++++ + + + + + + + + + + + + + + +""" +TABLE_BODY_START = """ +""" +TABLE_ROW_START = """ +""" +TABLE_ROW_COORDS = """ +""" +TABLE_DATA = """ +""" +HTML_ATTACK = """×""" +HTML_PIECE = """{symbol}""" +TABLE_ROW_END = """ +""" +TABLE_BODY_END = """ +""" +TABLE_FOOT_COORDS = """ + + + + + + + + + + + + + +""" +TABLE_END = """
abcdefgh
{rank}{attack}{piece}
abcdefgh
+""" + +def piece(piece: chess.Piece): + """Renders the given :class:`chess.Piece` as HTML escaped unicode. + + >>> import chess + >>> import chess.html + >>> + >>> chess.html.piece(chess.Piece.from_symbol("R")) # doctest: +SKIP + '' + """ + symb = piece.unicode_symbol().encode('ascii', 'xmlcharrefreplace').decode() + data = {'symbol': symb, 'color': chess.COLOR_NAMES[piece.color], + 'name': chess.PIECE_NAMES[piece.piece_type]} + return HTML_PIECE.format(**data) + + +def board(board: chess.Board, *, squares=[], flipped=False, coordinates=True, + lastmove=None, check=None, snippet=True, style=None): + """Renders a board with pieces and/or selected squares as an HTML table. + + :param board: A :class:`chess.BaseBoard` for a chessboard with pieces or + ``None`` (the default) for a chessboard without pieces. + :param squares: A :class:`chess.SquareSet` with selected squares. + :param flipped: Pass ``True`` to flip the board. + :param coordinates: Pass ``False`` to disable coordinates in the margin. + :param lastmove: A :class:`chess.Move` to be highlighted. + :param check: A square to be marked as check. + :param snippet: Pass ``False`` to return a complete HTML document. + :param style: A CSS stylesheet to include in the HTML document. Has no effect if ``snippet`` is ``True``. + + >>> import chess + >>> import chess.html + >>> + >>> board = chess.Board("8/8/8/8/4N3/8/8/8 w - - 0 1") + >>> squares = board.attacks(chess.E4) + >>> html = chess.html.board(board=board, squares=squares, snippet=False) # doctest: +SKIP + >>> with open('output.html', 'w') as f: + >>> f.write(html) + """ + if snippet: + html = TABLE_START + else: + if not style: + style = DEFAULT_STYLE + html = HTML_HEAD.format(style=style) + html += TABLE_START + if coordinates: + html += TABLE_HEAD_COORDS + if flipped: + rank_names = chess.RANK_NAMES + else: + rank_names = chess.RANK_NAMES[::-1] + html += TABLE_BODY_START + for rank in rank_names: + html += TABLE_ROW_START + if coordinates: + html += TABLE_ROW_COORDS.format(rank=rank) + for file in chess.FILE_NAMES: + square = chess.square(chess.FILE_NAMES.index(file), + chess.RANK_NAMES.index(rank)) + data = {'square': chess.square_name(square), 'attack': '', + 'check': '', 'lastmove': '', 'piece': ''} + if square in squares: + data['attack'] = HTML_ATTACK + if lastmove and square in [lastmove.from_square, + lastmove.to_square]: + data['lastmove'] = 'lastmove' + if square in board.piece_map(): + data['piece'] = piece(board.piece_map()[square]) + if square == check: + data['piece'] = data['piece'].replace('not-check', 'check') + html += TABLE_DATA.format(**data) + if coordinates: + html += TABLE_ROW_COORDS.format(rank=rank) + html += TABLE_ROW_END + html += TABLE_BODY_END + if coordinates: + html += TABLE_FOOT_COORDS + html += TABLE_END + return html diff --git a/docs/html.rst b/docs/html.rst new file mode 100644 index 000000000..102164769 --- /dev/null +++ b/docs/html.rst @@ -0,0 +1,11 @@ +HTML rendering +============= + +The :mod:`chess.html` module renders pieces and boards as HTML code. Depending +on your needs, HTML can be an alternative to SVG used by :mod:`chess.svg`. +`You can view a comparison of the HTML and SVG here `_. + + +.. autofunction:: chess.html.piece + +.. autofunction:: chess.html.board diff --git a/docs/scholars_mate.html b/docs/scholars_mate.html new file mode 100644 index 000000000..4c8d116c1 --- /dev/null +++ b/docs/scholars_mate.html @@ -0,0 +1,194 @@ +python-chess output +

HTML Table Output

+++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
abcdefgh
88
77
66
55
44
33
22
11
abcdefgh
+
+

SVG Output

aabbccddeeffgghh1122334455667788
Generated by + python-chess/examples/html_output.py
diff --git a/examples/html_output.py b/examples/html_output.py new file mode 100644 index 000000000..a8378d7f6 --- /dev/null +++ b/examples/html_output.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +"""Renders scholar's mate in the file `scholars_mate.html`.""" + +import chess +import chess.html +import chess.svg + +def main(): + board = chess.Board() + chess.Move.from_uci("a8a1") in board.legal_moves + board.push_san("e4") + board.push_san("e5") + board.push_san("Qh5") + board.push_san("Nc6") + board.push_san("Bc4") + board.push_san("Nf6") + board.push_san("Qxf7") + table = chess.html.board(board, lastmove=board.peek(), + check=board.pieces(chess.KING,chess.BLACK).pop()) + svg = chess.svg.board(board, lastmove=board.peek(), size=800, + check=board.pieces(chess.KING,chess.BLACK).pop()) + html = """python-chess output +

HTML Table Output

{1}
+

SVG Output

{2}
+ """.format(chess.html.DEFAULT_STYLE, table, svg) + with open('scholars_mate.html', 'w') as f: + f.write(html) + +if __name__ == '__main__': + main()