From a3ed5b24762c5c7a84ecb8a650ca616d3e51f619 Mon Sep 17 00:00:00 2001 From: yuiseki Date: Mon, 5 Oct 2020 15:43:32 +0900 Subject: [PATCH 1/8] add SVG.py --- README.rst | 9 ++ shogi/SVG.py | 360 ++++++++++++++++++++++++++++++++++++++++++++++ shogi/__init__.py | 6 + 3 files changed, 375 insertions(+) create mode 100644 shogi/SVG.py diff --git a/README.rst b/README.rst index ac059b1..7e1f8cd 100644 --- a/README.rst +++ b/README.rst @@ -105,6 +105,15 @@ Features +---------------------------+ 先手の持駒: 銀 +* Render board as SVG. + + .. code:: python + + >>> print(board.svg()) + + ... + + * Detects checkmates, stalemates. .. code:: python diff --git a/shogi/SVG.py b/shogi/SVG.py new file mode 100644 index 0000000..0ae69a3 --- /dev/null +++ b/shogi/SVG.py @@ -0,0 +1,360 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the python-shogi library. +# +# Copyright (C) 2020- Yui Matsumura +# 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 . + +def sfen_to_svg(sfen): + svg = """ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + > + + > + + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + + + + + + + + + + + + + + + + + + """ + + reftable = { + 'l': 'white-lance', + 'n': 'white-knight', + 's': 'white-silver', + 'g': 'white-gold', + 'k': 'white-king', + 'r': 'white-rook', + 'b': 'white-bishop', + 'p': 'white-pawn', + 'L': 'black-lance', + 'N': 'black-knight', + 'S': 'black-silver', + 'G': 'black-gold', + 'K': 'black-king', + 'R': 'black-rook', + 'B': 'black-bishop', + 'P': 'black-pawn' + } + + # render pieces on board + sfen_conf = sfen.split()[0].split('/') + board = [] + colnum = 1 + rownum = 1 + for col in sfen_conf: + colnum = 1 + for p in col: + if p.isdecimal(): + colnum = colnum+1 + else: + x = colnum * 20 + y = (rownum * 20) - 10 + ref = reftable[p] + t = '' + use = t.format(ref, x, y) + board.append(use) + colnum += 1 + rownum += 1 + + # render owned pieces + sfen_ownp = sfen.split()[2] + ownp_idx = 0 + first_lower = True + if not sfen_ownp == '-': + quantity = 1 + lastpiece = '' + for p in sfen_ownp: + if p.isdecimal(): + quantity = int(p) + continue + else: + lastpiece = p + x = 0 + y = 0 + if not p.islower(): + x = -20 + y = -90 - (ownp_idx*15) + ref = reftable[lastpiece] + t = '' + use = t.format(ref, x, y) + board.append(use) + if quantity > 1: + y = y + 15 + t = '{}' + text = t.format(x, y, quantity) + board.append(text) + else: + if first_lower: + ownp_idx = 0 + first_lower = False + x = -230 + y = -130 + (ownp_idx*15) + ref = reftable[lastpiece] + t = '' + use = t.format(ref, x, y) + board.append(use) + if quantity > 1: + x = abs(x)-5 + y = abs(y)-5 + t = '{}' + text = t.format(x, y, quantity) + board.append(text) + ownp_idx += 1 + quantity = 1 + svg = svg+'\n'.join(board)+'\n' + return svg \ No newline at end of file diff --git a/shogi/__init__.py b/shogi/__init__.py index 992964b..57d0687 100644 --- a/shogi/__init__.py +++ b/shogi/__init__.py @@ -28,6 +28,8 @@ from .Move import * from .Piece import * from .Consts import * +from .SVG import * + PIECE_TYPES_WITHOUT_KING = [ PAWN, LANCE, KNIGHT, SILVER, @@ -1124,6 +1126,10 @@ def sfen(self): return ''.join(sfen) + def svg(self): + svg = SVG.sfen_to_svg(self.sfen()) + return svg + def set_sfen(self, sfen): ''' Parses a SFEN and sets the position from it. From 9d416ebb5364023d97d7ae271b2e5fb9c6fde2a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20G=C3=BCnter=20Herforth?= Date: Mon, 22 Feb 2021 23:07:49 +0100 Subject: [PATCH 2/8] fixed: svg generation - added support for the promoted pieces - shifted some values for the pieces in hand - altered shadow drop --- shogi/SVG.py | 128 +++++++++++++++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 56 deletions(-) diff --git a/shogi/SVG.py b/shogi/SVG.py index 0ae69a3..c86886a 100644 --- a/shogi/SVG.py +++ b/shogi/SVG.py @@ -36,11 +36,12 @@ def sfen_to_svg(sfen): - + + - + @@ -128,7 +129,7 @@ def sfen_to_svg(sfen): - + @@ -139,7 +140,7 @@ def sfen_to_svg(sfen): - + @@ -150,7 +151,7 @@ def sfen_to_svg(sfen): - + @@ -161,7 +162,7 @@ def sfen_to_svg(sfen): - + @@ -172,7 +173,7 @@ def sfen_to_svg(sfen): - + @@ -183,7 +184,7 @@ def sfen_to_svg(sfen): - + @@ -291,70 +292,85 @@ def sfen_to_svg(sfen): 'P': 'black-pawn' } + reftablepromote = { + 'l': 'white-pro-lance', + 'n': 'white-pro-knight', + 's': 'white-pro-silver', + 'r': 'white-dragon', + 'b': 'white-horse', + 'p': 'white-pro-pawn', + 'L': 'black-pro-lance', + 'N': 'black-pro-knight', + 'S': 'black-pro-silver', + 'R': 'black-dragon', + 'B': 'black-horse', + 'P': 'black-pro-pawn' + } + # render pieces on board sfen_conf = sfen.split()[0].split('/') board = [] - colnum = 1 - rownum = 1 - for col in sfen_conf: + promoted = False + + for row_index, row in enumerate(sfen_conf): colnum = 1 - for p in col: - if p.isdecimal(): - colnum = colnum+1 + for col in row: + if col.isdecimal(): + colnum += int(col) + elif col == '+': + promoted = True else: - x = colnum * 20 - y = (rownum * 20) - 10 - ref = reftable[p] + x = (colnum) * 20 + y = ((row_index + 1) * 20) - 10 + if promoted: + ref = reftablepromote[col] + else: + ref = reftable[col] t = '' use = t.format(ref, x, y) board.append(use) - colnum += 1 - rownum += 1 + colnum += 1 + promoted = False # render owned pieces sfen_ownp = sfen.split()[2] - ownp_idx = 0 - first_lower = True + ownp_idx_WHITE = 0 + ownp_idx_BLACK = 0 + quantity = 1 + if not sfen_ownp == '-': - quantity = 1 - lastpiece = '' for p in sfen_ownp: if p.isdecimal(): quantity = int(p) continue + + if not p.islower(): + x = 212 + y = 115 - (ownp_idx_WHITE*18) + if promoted: + ref = f'' + ref = reftable[p] + t = '' + use = t.format(ref, x, y) + ownp_idx_WHITE += 1 + board.append(use) + if quantity > 1: + t = '{}' + text = t.format((x+2), (y+7), quantity) + board.append(text) else: - lastpiece = p - x = 0 - y = 0 - if not p.islower(): - x = -20 - y = -90 - (ownp_idx*15) - ref = reftable[lastpiece] - t = '' - use = t.format(ref, x, y) - board.append(use) - if quantity > 1: - y = y + 15 - t = '{}' - text = t.format(x, y, quantity) - board.append(text) - else: - if first_lower: - ownp_idx = 0 - first_lower = False - x = -230 - y = -130 + (ownp_idx*15) - ref = reftable[lastpiece] - t = '' - use = t.format(ref, x, y) - board.append(use) - if quantity > 1: - x = abs(x)-5 - y = abs(y)-5 - t = '{}' - text = t.format(x, y, quantity) - board.append(text) - ownp_idx += 1 - quantity = 1 + x = 0 + y = 65 + (ownp_idx_BLACK*18) + ref = reftable[p] + t = '' + use = t.format(ref, x, y) + ownp_idx_BLACK += 1 + board.append(use) + if quantity > 1: + t = '{}' + text = t.format(x, (y+16), quantity) + board.append(text) + quantity = 1 + svg = svg+'\n'.join(board)+'\n' return svg \ No newline at end of file From 8573c56c1ab394de8013340dcbfa5a25678a2fde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20G=C3=BCnter=20Herforth?= Date: Tue, 23 Feb 2021 10:03:43 +0100 Subject: [PATCH 3/8] added: basic unit test file for svg - check pieces for initial board position - check pieces for complex board position --- shogi/SVG.py | 3 ++- tests/svg_test.py | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests/svg_test.py diff --git a/shogi/SVG.py b/shogi/SVG.py index c86886a..8642eaf 100644 --- a/shogi/SVG.py +++ b/shogi/SVG.py @@ -270,7 +270,7 @@ def sfen_to_svg(sfen): - + """ reftable = { @@ -337,6 +337,7 @@ def sfen_to_svg(sfen): ownp_idx_WHITE = 0 ownp_idx_BLACK = 0 quantity = 1 + board.append('') if not sfen_ownp == '-': for p in sfen_ownp: diff --git a/tests/svg_test.py b/tests/svg_test.py new file mode 100644 index 0000000..909880d --- /dev/null +++ b/tests/svg_test.py @@ -0,0 +1,50 @@ +import shogi +import unittest + + + +class BoardTestCase(unittest.TestCase): + def test_default_board(self): + board = shogi.Board() + svg_file = board.svg() + pieces = svg_file.split('')[1] + board_positions = pieces.split('')[0] + hand_positions = pieces.split('')[1] + + # check board pieces + self.assertTrue(board_positions.count('bishop') == 2) + self.assertTrue(board_positions.count('rook') == 2) + self.assertTrue(board_positions.count('pawn') == 18) + self.assertTrue(board_positions.count('knight') == 4) + self.assertTrue(board_positions.count('king') == 2) + self.assertTrue(board_positions.count('gold') == 4) + self.assertTrue(board_positions.count('silver') == 4) + self.assertTrue(board_positions.count('lance') == 4) + + def test_complex_position(self): + board = shogi.Board('4+R3l/1r1+P2gk1/3p1p1s1/2pg3pp/1p4p2/SP4PPP/2NP1PKS1/2G2+n3/8L w B2N2L3Pbgsp 10') + svg_file = board.svg() + pieces = svg_file.split('')[1] + board_positions = pieces.split('')[0] + hand_positions = pieces.split('')[1] + + # check board pieces + self.assertTrue(board_positions.count('bishop') == 0) + self.assertTrue(board_positions.count('rook') == 1) + self.assertTrue(board_positions.count('pawn') == 14) + self.assertTrue(board_positions.count('knight') == 2) + self.assertTrue(board_positions.count('king') == 2) + self.assertTrue(board_positions.count('gold') == 3) + self.assertTrue(board_positions.count('silver') == 3) + self.assertTrue(board_positions.count('lance') == 2) + self.assertTrue(board_positions.count('dragon') == 1) + self.assertTrue(board_positions.count('pro-pawn') == 1) + self.assertTrue(board_positions.count('pro-knight') == 1) + + # check hand pieces (only checking 1 count per player) + self.assertTrue(hand_positions.count('bishop') == 2) + self.assertTrue(hand_positions.count('gold') == 1) + self.assertTrue(hand_positions.count('silver') == 1) + self.assertTrue(hand_positions.count('pawn') == 2) + self.assertTrue(hand_positions.count('knight') == 1) + self.assertTrue(hand_positions.count('lance') == 1) From 1b0f07235b3dc656153f30eb11e269612b6fb359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20G=C3=BCnter=20Herforth?= Date: Tue, 23 Feb 2021 10:05:28 +0100 Subject: [PATCH 4/8] added: header information to svg_test file --- tests/svg_test.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/svg_test.py b/tests/svg_test.py index 909880d..12c7a5b 100644 --- a/tests/svg_test.py +++ b/tests/svg_test.py @@ -1,8 +1,25 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the python-shogi library. +# +# Copyright (C) 2020- Yui Matsumura +# 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 . + import shogi import unittest - class BoardTestCase(unittest.TestCase): def test_default_board(self): board = shogi.Board() From 58e36f99338df072c0b7b54b1ad8c3fb01e1fb3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20G=C3=BCnter=20Herforth?= Date: Sat, 27 Feb 2021 23:22:35 +0100 Subject: [PATCH 5/8] fixed: issue when player had more than 9 of a piece in hand - quantity would be overwritten with the second digit of the number ie. if sente has 12 pawns, the svg would say he has 2 pawns - to fix, changed the quantity to a string and converted to int before using the value --- shogi/SVG.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/shogi/SVG.py b/shogi/SVG.py index 8642eaf..1c46252 100644 --- a/shogi/SVG.py +++ b/shogi/SVG.py @@ -342,10 +342,15 @@ def sfen_to_svg(sfen): if not sfen_ownp == '-': for p in sfen_ownp: if p.isdecimal(): - quantity = int(p) + + quantity = ''.join((quantity, p)) continue if not p.islower(): + if quantity: + quantity = int(quantity) + else: + quantity = 1 x = 212 y = 115 - (ownp_idx_WHITE*18) if promoted: @@ -360,6 +365,10 @@ def sfen_to_svg(sfen): text = t.format((x+2), (y+7), quantity) board.append(text) else: + if quantity: + quantity = int(quantity) + else: + quantity = 1 x = 0 y = 65 + (ownp_idx_BLACK*18) ref = reftable[p] @@ -371,7 +380,7 @@ def sfen_to_svg(sfen): t = '{}' text = t.format(x, (y+16), quantity) board.append(text) - quantity = 1 + quantity = '' svg = svg+'\n'.join(board)+'\n' return svg \ No newline at end of file From d8ae9bc6b76810bbafcf9ea6cdf4d74c07bd93c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20G=C3=BCnter=20Herforth?= Date: Sun, 28 Feb 2021 00:11:00 +0100 Subject: [PATCH 6/8] added: svg image to readme and how to save it as a file - added file saving example for both python 2.7 and 3 --- README.rst | 5 + data/images/board.svg | 296 ++++++++++++++++++++++++++++++++++++++++++ shogi/SVG.py | 3 +- 3 files changed, 302 insertions(+), 2 deletions(-) create mode 100644 data/images/board.svg diff --git a/README.rst b/README.rst index 7e1f8cd..9f5967b 100644 --- a/README.rst +++ b/README.rst @@ -113,6 +113,11 @@ Features ... + >>> with open('board.svg', 'w') as tfile: + ... print >> tfile, board.svg() # python 2.7 + ... print(board.svg(), file=tfile) # python 3+ + + .. image:: data/images/board.svg * Detects checkmates, stalemates. diff --git a/data/images/board.svg b/data/images/board.svg new file mode 100644 index 0000000..1afa36b --- /dev/null +++ b/data/images/board.svg @@ -0,0 +1,296 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + > + + > + + > + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 9 + 8 + 7 + 6 + 5 + 4 + 3 + 2 + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/shogi/SVG.py b/shogi/SVG.py index 1c46252..758a15a 100644 --- a/shogi/SVG.py +++ b/shogi/SVG.py @@ -342,7 +342,6 @@ def sfen_to_svg(sfen): if not sfen_ownp == '-': for p in sfen_ownp: if p.isdecimal(): - quantity = ''.join((quantity, p)) continue @@ -354,7 +353,7 @@ def sfen_to_svg(sfen): x = 212 y = 115 - (ownp_idx_WHITE*18) if promoted: - ref = f'' + ref = '' ref = reftable[p] t = '' use = t.format(ref, x, y) From 3be5e32ec3a5981bcbecee5cc0a5584d808976d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20G=C3=BCnter=20Herforth?= Date: Sun, 28 Feb 2021 00:17:16 +0100 Subject: [PATCH 7/8] fix: centered svg image in README --- README.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/README.rst b/README.rst index 9f5967b..c24f7d2 100644 --- a/README.rst +++ b/README.rst @@ -118,6 +118,7 @@ Features ... print(board.svg(), file=tfile) # python 3+ .. image:: data/images/board.svg + :align: center * Detects checkmates, stalemates. From 52afb4d23c42be55c0ebfa282b84f09ccfe97aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20G=C3=BCnter=20Herforth?= Date: Sun, 28 Feb 2021 01:15:29 +0100 Subject: [PATCH 8/8] added: another unittest to test for double digits of pieces in hand --- shogi/SVG.py | 2 -- tests/svg_test.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/shogi/SVG.py b/shogi/SVG.py index 758a15a..b9695f7 100644 --- a/shogi/SVG.py +++ b/shogi/SVG.py @@ -352,8 +352,6 @@ def sfen_to_svg(sfen): quantity = 1 x = 212 y = 115 - (ownp_idx_WHITE*18) - if promoted: - ref = '' ref = reftable[p] t = '' use = t.format(ref, x, y) diff --git a/tests/svg_test.py b/tests/svg_test.py index 12c7a5b..b10148d 100644 --- a/tests/svg_test.py +++ b/tests/svg_test.py @@ -65,3 +65,33 @@ def test_complex_position(self): self.assertTrue(hand_positions.count('pawn') == 2) self.assertTrue(hand_positions.count('knight') == 1) self.assertTrue(hand_positions.count('lance') == 1) + + def test_two_digit_in_hand(self): + # 11 pawns in sente's hand + board = shogi.Board('4+R3l/1r1+P2gk1/3p3s1/2pg5/1p4p2/SP7/2N2PKS1/2G2+n3/8L b b2n2lBGS11P 50') + svg_file = board.svg() + pieces = svg_file.split('')[1] + board_positions = pieces.split('')[0] + hand_positions = pieces.split('')[1] + + # check board pieces + self.assertTrue(board_positions.count('bishop') == 0) + self.assertTrue(board_positions.count('rook') == 1) + self.assertTrue(board_positions.count('pawn') == 7) + self.assertTrue(board_positions.count('knight') == 2) + self.assertTrue(board_positions.count('king') == 2) + self.assertTrue(board_positions.count('gold') == 3) + self.assertTrue(board_positions.count('silver') == 3) + self.assertTrue(board_positions.count('lance') == 2) + self.assertTrue(board_positions.count('dragon') == 1) + self.assertTrue(board_positions.count('pro-pawn') == 1) + self.assertTrue(board_positions.count('pro-knight') == 1) + + # check hand pieces (only checking 1 count per player) + self.assertTrue(hand_positions.count('bishop') == 2) + self.assertTrue(hand_positions.count('gold') == 1) + self.assertTrue(hand_positions.count('silver') == 1) + self.assertTrue(hand_positions.count('pawn') == 1) + self.assertTrue(hand_positions.count('knight') == 1) + self.assertTrue(hand_positions.count('lance') == 1) + self.assertTrue(hand_positions.count('>11<') == 1)