diff --git a/README.md b/README.md index 2da7b9cf..555118a6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # pydraughts [![PyPI version](https://badge.fury.io/py/pydraughts.svg)](https://badge.fury.io/py/pydraughts) [![Tests](https://github.com/AttackingOrDefending/pydraughts/actions/workflows/tests.yml/badge.svg)](https://github.com/AttackingOrDefending/pydraughts/actions/workflows/tests.yml) [![Build](https://github.com/AttackingOrDefending/pydraughts/actions/workflows/build.yml/badge.svg)](https://github.com/AttackingOrDefending/pydraughts/actions/workflows/build.yml) [![CodeQL](https://github.com/AttackingOrDefending/pydraughts/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/AttackingOrDefending/pydraughts/actions/workflows/codeql-analysis.yml) [![codecov](https://codecov.io/gh/AttackingOrDefending/pydraughts/branch/main/graph/badge.svg?token=ZSPXIVSAWN)](https://codecov.io/gh/AttackingOrDefending/pydraughts) -pydraughts is a draughts (checkers) library for Python with move generation, PDN reading and writing, engine communication and balloted openings. It is based on [ImparaAI/checkers](https://github.com/ImparaAI/checkers). +pydraughts is a draughts (checkers) library for Python with move generation, SVG visualizations, PDN reading and writing, engine communication and balloted openings. It is based on [ImparaAI/checkers](https://github.com/ImparaAI/checkers). Installing ---------- @@ -48,7 +48,13 @@ board.push(move) board2 = Board(fen="W:WK40:B19,29") board2.push(Move(board2, pdn_move='40x14')) ``` -* Get a visual representation of the board +* Get a visual representation of the board as SVG +```python +from draughts import svg +svg.create_svg(Board(fen="B:W16,19,33,34,47,K4:B17,25,26")) +``` +![SVG Board](examples/board.svg) +* Get a visual representation of the board in the terminal ```python print(board) diff --git a/draughts/__init__.py b/draughts/__init__.py index 5387952c..504a4266 100644 --- a/draughts/__init__.py +++ b/draughts/__init__.py @@ -3,5 +3,5 @@ __all__ = ["Board", "Move", "WHITE", "BLACK"] __author__ = "Ioannis Pantidis" -__copyright__ = "2021-2024, " + __author__ -__version__ = "0.6.6" +__copyright__ = "2021-2025, " + __author__ +__version__ = "0.6.7" diff --git a/draughts/svg.py b/draughts/svg.py new file mode 100644 index 00000000..64816d85 --- /dev/null +++ b/draughts/svg.py @@ -0,0 +1,94 @@ +import math +from draughts import Board + + +def create_svg(board: Board) -> str: + """Create an SVG of a board.""" + # Base square size + square_size = 40 + margin = 16 # Fixed margin size for coordinates + + # Calculate SVG dimensions based on board size + str_representation = list(map(lambda row_str: row_str.split("|"), filter(lambda row_str: "|" in row_str, str(board).split("\n")))) + width = len(str_representation[0]) + height = len(str_representation) + svg_width = (square_size * width) + (2 * margin) + svg_height = (square_size * height) + (2 * margin) + + # Background color for coordinates + svg = [f''' +'''] + + # Add coordinates in white + for i in range(width): + # Letters along bottom + svg.append(f'{chr(97 + i)}') + + for i in range(height): + # Numbers along left side + svg.append(f'{height - i}') + + # Draw board + for row in range(height): + for col in range(width): + x = margin + col * square_size + y = margin + row * square_size + color = "#E8D0AA" if (row + col) % 2 == 0 else "#B87C4C" + svg.append(f'') + + # Draw pieces + for row, row_str in enumerate(str_representation): + for col, piece in enumerate(row_str): + piece = piece.strip() + if not piece: + continue + + # Center of square + cx = margin + col * square_size + square_size // 2 + cy = margin + row * square_size + square_size // 2 + + piece_radius = square_size * 0.4 + piece_color = "#000000" if piece.lower() == 'b' else "#FFFFFF" + stroke_color = "#FFFFFF" if piece.lower() == 'b' else "#000000" + + # Draw main piece + svg.append(f'') + + # Enhanced crown for kings + if piece.isupper(): + gradient_id = f"crown_gradient_{cx}_{cy}" + svg.append(f''' + + + + + +''') + + # Draw 5-pointed star + num_points = 5 + outer_radius = piece_radius * 0.5 + inner_radius = outer_radius * 0.382 + points = [] + + for i in range(num_points * 2): + angle = (i * math.pi / num_points) - (math.pi / 2) + radius = outer_radius if i % 2 == 0 else inner_radius + x = cx + radius * math.cos(angle) + y = cy + radius * math.sin(angle) + points.append(f"{x},{y}") + + svg.append(f'') + + svg.append('') + return '\n'.join(svg) diff --git a/examples/board.svg b/examples/board.svg new file mode 100644 index 00000000..b98acd88 --- /dev/null +++ b/examples/board.svg @@ -0,0 +1,140 @@ + + +a +b +c +d +e +f +g +h +i +j +10 +9 +8 +7 +6 +5 +4 +3 +2 +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index a3f79368..fa22d0b0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ name = pydraughts version = attr: draughts.__version__ author = Ioannis Pantidis -description = A draughts library for Python with move generation, PDN reading and writing, engine communication and balloted openings +description = A draughts library for Python with move generation, SVG visualizations, PDN reading and writing, engine communication and balloted openings long_description = file: README.md long_description_content_type = text/markdown keywords = checkers, draughts, game, fen, pdn diff --git a/test_pydraughts/test_engines.py b/test_pydraughts/test_engines.py index 291ff3b8..5ac0270c 100644 --- a/test_pydraughts/test_engines.py +++ b/test_pydraughts/test_engines.py @@ -211,6 +211,7 @@ def test_checkerboard_engines(): break logger.info('Finished playing 1') checkerboard.kill_process() + logger.info('Closed engines 1') checkerboard = CheckerBoardEngine('cake_189f.dll') limit = Limit(10) @@ -225,6 +226,7 @@ def test_checkerboard_engines(): break logger.info('Finished playing 2') checkerboard.kill_process() + logger.info('Closed engines 2') @pytest.mark.timeout(300, method="thread") @@ -245,6 +247,7 @@ def test_russian_checkerboard_engines(): break logger.info('Finished playing 1') checkerboard.kill_process() + logger.info('Closed engines 1') checkerboard = CheckerBoardEngine('kestog.dll') limit = Limit(10) @@ -259,6 +262,7 @@ def test_russian_checkerboard_engines(): break logger.info('Finished playing 2') checkerboard.kill_process() + logger.info('Closed engines 2') @pytest.mark.timeout(450, method="thread") diff --git a/test_pydraughts/test_pdn.py b/test_pydraughts/test_pdn.py index 40c4170a..2e703bf2 100644 --- a/test_pydraughts/test_pdn.py +++ b/test_pydraughts/test_pdn.py @@ -12,7 +12,7 @@ def download_games(): headers = {'User-Agent': 'User Agent', 'From': 'mail@mail.com'} - response = requests.get('https://pdn.fmjd.org/_downloads/games.zip', headers=headers, allow_redirects=True) + response = requests.get('https://github.com/wiegerw/pdn/raw/refs/heads/master/games/games.zip', headers=headers, allow_redirects=True) with open('./TEMP/games.zip', 'wb') as file: file.write(response.content) with zipfile.ZipFile('./TEMP/games.zip', 'r') as zip_ref: diff --git a/test_pydraughts/test_svg.py b/test_pydraughts/test_svg.py new file mode 100644 index 00000000..36232128 --- /dev/null +++ b/test_pydraughts/test_svg.py @@ -0,0 +1,148 @@ +from draughts import Board, Move +from draughts.svg import create_svg + + +def test_svg(): + board = Board(fen="W:WK4,19,27,33,34,47:B11,12,17,22,25,26:H0:F50") + board.push(Move(board, pdn_move="27x16")) + svg = create_svg(board) + assert svg == """ + +a +b +c +d +e +f +g +h +i +j +10 +9 +8 +7 +6 +5 +4 +3 +2 +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +"""