python-3.xsvgpyqt5python-chess

Get clicked chess piece from an SVG chessboard


I am developing a chess GUI in Python 3.6.3 using PyQt5 5.9.1 (GUI framework) and python-chess 0.21.1 (chess library) on Windows 10. I want to get the value of a piece that was clicked on an SVG chessboard (provided by python-chess) so that I can then move that piece to another square.

After the first left mouse click and getting the piece, I want to get the second left mouse click from the user and get the square that the user clicked on. Then my chess GUI must move the piece from originating square to the target square.

So, here's my complete working code so far. Any hints or actual code additions are very welcome.

import chess
import chess.svg
from PyQt5.QtSvg import QSvgWidget
from PyQt5.QtCore import pyqtSlot, Qt
from PyQt5.QtWidgets import QApplication, QWidget


class MainWindow(QWidget):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Chess Titan")
        self.setGeometry(300, 300, 800, 800)

        self.widgetSvg = QSvgWidget(parent=self)
        self.widgetSvg.setGeometry(10, 10, 600, 600)

        self.chessboard = chess.Board()

    @pyqtSlot(QWidget)
    def mousePressEvent(self, event):
        if event.buttons() == Qt.LeftButton:
            ## How to get the clicked SVG chess piece?

            # Envoke the paint event.
            self.update()

    @pyqtSlot(QWidget)
    def paintEvent(self, event):
        self.chessboardSvg = chess.svg.board(self.chessboard).encode("UTF-8")
        self.widgetSvg.load(self.chessboardSvg)


if __name__ == "__main__":
    chessTitan = QApplication([])
    window = MainWindow()
    window.show()
    chessTitan.exec()

Solution

  • If size of chessboard is known, you can find the coordinates of the mouseclick from event.pos() resp.event.x(), event.y() depending on marginwidth and squaresize, see chess.svg.py line 129 ff.

    edit Nov 25: event.pos() is in this example in MainWindow coordinates, to find the coordinates on chessboard all must be calculated from top left corner represented by self.svgX and self.svgY:

    import chess
    import chess.svg
    from PyQt5.QtCore import pyqtSlot, Qt
    from PyQt5.QtSvg import QSvgWidget
    from PyQt5.QtWidgets import QApplication, QWidget
    
    
    class MainWindow(QWidget):
        def __init__(self):
            super().__init__()
    
            self.setWindowTitle("Chess Titan")
            self.setGeometry(300, 300, 800, 800)
    
            self.widgetSvg = QSvgWidget(parent=self)
            self.svgX = 50                          # top left x-pos of chessboard
            self.svgY = 50                          # top left y-pos of chessboard
            self.cbSize = 600                       # size of chessboard
            self.widgetSvg.setGeometry(self.svgX,self.svgY, self.cbSize, self.cbSize)
            self.coordinates = True
            # see chess.svg.py line 129
            self.margin = 0.05*self.cbSize if self.coordinates == True else 0
            self.squareSize  = (self.cbSize - 2 * self.margin) / 8.0
            self.chessboard = chess.Board()
            self.pieceToMove = [None, None]
    
        @pyqtSlot(QWidget)
        def mousePressEvent(self, event):
            if self.svgX < event.x() <= self.svgX + self.cbSize and self.svgY < event.y() <= self.svgY + self.cbSize:   # mouse on chessboard
                if event.buttons() == Qt.LeftButton:
                    # if the click is on chessBoard only
                    if self.svgX + self.margin < event.x() < self.svgX + self.cbSize - self.margin and self.svgY + self.margin < event.y() < self.svgY + self.cbSize - self.margin:
                        file = int((event.x() - (self.svgX + self.margin))/self.squareSize)             
                        rank = 7 - int((event.y() - (self.svgY + self.margin))/self.squareSize) 
                        square = chess.square(file, rank)                       # chess.sqare.mirror() if white is on top
                        piece = self.chessboard.piece_at(square)
                        coordinates = '{}{}'.format(chr(file + 97), str(rank +1))       
                        if self.pieceToMove[0] is not None:
                            move = chess.Move.from_uci('{}{}'.format(self.pieceToMove[1], coordinates))
                            self.chessboard.push(move)
                            print(self.chessboard.fen())
                            piece = None
                            coordinates= None
                        self.pieceToMove = [piece, coordinates]                                           
                    else:
                        print('coordinates clicked')
                    # Envoke the paint event.
                    self.update()
            else:
                QWidget.mousePressEvent(self, event)
    
        @pyqtSlot(QWidget)
        def paintEvent(self, event):
            self.chessboardSvg = chess.svg.board(self.chessboard, size = self.cbSize, coordinates = self.coordinates).encode("UTF-8")
            self.widgetSvg.load(self.chessboardSvg)
    
    
    if __name__ == "__main__":
        chessTitan = QApplication([])
        window = MainWindow()
        window.show()
        chessTitan.exec()
    

    move white and black pieces alternating, they change the color if the same color is moved twice.