1

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()
Boštjan Mejak
  • 827
  • 9
  • 23

3 Answers3

3

Below is the Python, PyQt5 and python-chess code for a fully functional chess GUI that has legal move detection built in, so chess piece movement behaves according to the rules of chess.

#! /usr/bin/env python

"""
This module is the execution point of the chess GUI application.
"""

import sys

import chess

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


class MainWindow(QWidget):
    """
    Create a surface for the chessboard.
    """
    def __init__(self):
        """
        Initialize the chessboard.
        """
        super().__init__()

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

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

        self.boardSize = min(self.widgetSvg.width(),
                             self.widgetSvg.height())
        self.coordinates = True
        self.margin = 0.05 * self.boardSize if self.coordinates else 0
        self.squareSize = (self.boardSize - 2 * self.margin) / 8.0
        self.pieceToMove = [None, None]

        self.board = chess.Board()
        self.drawBoard()

    @pyqtSlot(QWidget)
    def mousePressEvent(self, event):
        """
        Handle left mouse clicks and enable moving chess pieces by
        clicking on a chess piece and then the target square.

        Moves must be made according to the rules of chess because
        illegal moves are suppressed.
        """
        if event.x() <= self.boardSize and event.y() <= self.boardSize:
            if event.buttons() == Qt.LeftButton:
                if self.margin < event.x() < self.boardSize - self.margin and self.margin < event.y() < self.boardSize - self.margin:
                    file = int((event.x() - self.margin) / self.squareSize)
                    rank = 7 - int((event.y() - self.margin) / self.squareSize)
                    square = chess.square(file, rank)
                    piece = self.board.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))
                        if move in self.board.legal_moves:
                            self.board.push(move)
                        piece = None
                        coordinates = None
                    self.pieceToMove = [piece, coordinates]
                    self.drawBoard()

    def drawBoard(self):
        """
        Draw a chessboard with the starting position and then redraw
        it for every new move.
        """
        self.boardSvg = self.board._repr_svg_().encode("UTF-8")
        self.drawBoardSvg = self.widgetSvg.load(self.boardSvg)

        return self.drawBoardSvg


if __name__ == "__main__":
    chessGui = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(chessGui.exec_())
Boštjan Mejak
  • 827
  • 9
  • 23
2

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.

a_manthey_67
  • 4,046
  • 1
  • 17
  • 28
  • How would I go and implement the rules? So White/Black can't move twice or more times. First moves White and then Black. Also, chess pieces must not be placed randomly on the board, but by the rules of chess. I know that python-chess has this `chess.Board().is_valid()` function, I just don't know where exactly should I put it. By implementing the `is_valid()` function, we will ensure alternative piece moves (White, Black ...) and also ensure the pieces are moved according to chess rules. Also, how can I implement that gradient color on the King's square if it is in check? – Boštjan Mejak Nov 16 '17 at 15:18
  • Anyway, thank you for your solution! I really appreciate the help. I've got one question though. Why is this necessary? `else: QWidget.mousePressEvent(self, event)`? – Boštjan Mejak Nov 16 '17 at 16:50
  • The `mousePressEvent()` is defined on `MainWindow`. By the if-thread it only acts if chessboard is clicked. With this line you have the standard `mousePressEvent()` on the other part of MainWindow. – a_manthey_67 Nov 16 '17 at 18:36
  • I have problem with the margin. This number 0.05 does not work anymore since I've changed the geometry of the SVG widget. The position of the widget is no longer (10, 10) because I need it to be (10, 50) now that I've added a toolbar. How can I calculate this margin number now? I've looked up chess/svg.py and saw that margin is 20 if coordinates are enabled, but since I had my widget's geometry set to (10, 10), you had to multiply by 0.05 to get a margin of 30 (0.05 × 600 [my boardSize] = 30). But now I have a strange position of the SVG widget where x is 10 and y is 50. – Boštjan Mejak Nov 25 '17 at 02:32
  • Thank you. Does the position of the SVG widget has to be quadratic? I’ve noticed you gave x and y both a value of 50. Is it possible to have x 10 and y 50? That’ll be cool. – Boštjan Mejak Nov 25 '17 at 12:32
  • you can set them to different values. – a_manthey_67 Nov 25 '17 at 13:56
  • see my second edit in `paintEvent()`. Try to set `self.coordinates = False`. – a_manthey_67 Nov 25 '17 at 16:15
  • Can we somehow get rid of that 0.05 number? I don’t like hardcoded stuff. Is there a way? – Boštjan Mejak Nov 25 '17 at 23:04
0

a_manthey_67 and Boštjan Mejak, I've combined features from both of your solutions: https://github.com/vtad4f/chess-ui/blob/master/board.py

The full version integrates AI player(s) with your board UI:

vtad4f
  • 1
  • @a_manthey_67 and Boštjan Mejak, any objection to me using a MIT license and adding you as contributors? – vtad4f Sep 08 '19 at 18:47
  • Using the MIT License is a wonderful idea. I'm also using the MIT License for my application. I have made so much progress since I asked this question. So yes, @vtad4f, you can add me as a contributor. – Boštjan Mejak Oct 26 '21 at 15:32