I'm building a chess engine using pygame as a personal project. I was following a tutorial but I want to go further. For the moment I have the chessboard, the pieces in their initial position and the possibility to undo the last move (key z). Also, all pieces can be moved by clicking on them and then clicking on the arrival square. Without any verification: you can eat your own king with a pawn on the 1st turn, and ignore the alternation of players. The only condition for ending the game is the event that closes its window. I'd just like to make it possible to drag&drop pieces, but I can't do it. I haven't found a tutorial that shows a practical example. It's always on a Rect object in a minimalist main.py with no design constraints. In my code, the management of player actions and the display are well separated. Could you please help me find a way to implement drag&drop that respects this as much as possible? Or explain why this isn't possible. thanks for reading ChessMain_3.py """ This is our main driver file. It will be responsible for handling user input and displaying the current GameState object. """ import pygame as p import ChessEngine_3 WIDTH = HEIGHT = 512 #400 is another option DIMENSION = 8 #dimension of a chess board is 8x8 SQ_SIZE = HEIGHT // DIMENSION MAX_FPS = 15 #for animations later on IMAGES = {} #dictionnary of images """ Initialize a global dictionnary of images. It will be called exactly once in the main """ def loadImage(): pieces = ['bP', 'bR', 'bN', 'bB', 'bQ', 'bK', 'wP', 'wR', 'wN', 'wB', 'wQ', 'wK'] for piece in pieces: unscaledImage = p.image.load('images/' + piece + '.png') IMAGES[piece] = p.transform.scale(unscaledImage, (SQ_SIZE, SQ_SIZE)) #note: now can access an image by saying "IMAGES['bP']" """ The main driver for our code. This will handle user input and update graphics """ def main(): p.init() screen = p.display.set_mode((WIDTH, HEIGHT)) clock = p.time.Clock() screen.fill(p.Color('white')) gs = ChessEngine_3.GameState() loadImage() #only do this once before the while loop running = True sqSelected = () #no square is selected initially, keep track of the last click of the user (tuple: (row, col)) playerClicks = [] #keep track of player clicks (two tuples: [(6, 4), (4, 4)] ) while running: for e in p.event.get(): if e.type == p.QUIT: running = False #mouse handler elif e.type == p.MOUSEBUTTONDOWN: location = p.mouse.get_pos() #(x, y) location of the mouse #so make sure to take it in account when adding a side panel col = location[0] // SQ_SIZE row = location[1] // SQ_SIZE if sqSelected == (row, col): #the user clicked the same square twice sqSelected = () #deselect playerClicks = [] #clear the player clicks else: sqSelected = (row, col) playerClicks.append(sqSelected) #append for both 1st and 2nd clicks if len(playerClicks) == 2: #after 2nd click move = ChessEngine_3.Move(playerClicks[0], playerClicks[1], gs.board) print(move.getChessNotation()) gs.makeMove(move) sqSelected = () #reset user clicks playerClicks = [] #key handler elif e.type == p.KEYDOWN: #user wants to undo last move if e.key == p.K_z: #undo when z is pressed gs.undoMove() sqSelected = () #reset user clicks playerClicks = [] drawGameState(screen, gs) clock.tick(MAX_FPS) p.display.flip() """ Responsible for all the graphics within the current game state. """ def drawGameState(screen, gs): drawBoard(screen) #draw squares on the board #add in piece highlighting or move suggestions (later) drawPiece(screen, gs.board) #draw pieces on top of those squares """ Draw the squares on the board. """ def drawBoard(screen): # colors = [p.Color('white'), p.Color('black')] # colors = [p.Color('light yellow'), p.Color('brown')] # colors = [p.Color('antiquewhite1'), p.Color('burlywood4')] # colors = [p.Color('burlywood1'), p.Color('tan3')] colors = [p.Color('antiquewhite1'), p.Color('burlywood3')] for r in range(DIMENSION): for c in range(DIMENSION): color = colors[((r+c) % 2)] rectToDraw = p.Rect(c*SQ_SIZE, r*SQ_SIZE, SQ_SIZE, SQ_SIZE) #position then dimensions p.draw.rect(screen, color, rectToDraw) """ Draw the pieces on the board using the current GameState.board """ def drawPiece(screen, board): for r in range(DIMENSION): for c in range(DIMENSION): piece = board[r][c] if piece != '--': #not empty square rectWhereToDrawPiece = p.Rect(c*SQ_SIZE, r*SQ_SIZE, SQ_SIZE, SQ_SIZE) #position then dimensions screen.blit(IMAGES[piece], rectWhereToDrawPiece) if __name__ == '__main__': #google to understand why that "if" statement main() ChessEngine_3.py """ This class is responsible for storing all the information about the current state of a chess game. It will also be responsible for determining the valid moves at the current state. It will also keep a move log. """ class GameState(): def __init__(self): #Board is 8x8 2d list, each element of the list has 2 characters #The first character represent the color of the piece: 'b' or 'w' #The secind character represent the type of the piece: 'P', 'R', 'N', ... #'--' represents an empty square with no piece self.board = [ ["bR", "bN", "bB", "bQ", "bK", "bB", "bN", "bR"], ["bP", "bP", "bP", "bP", "bP", "bP", "bP", "bP"], ["--", "--", "--", "--", "--", "--", "--", "--"], ["--", "--", "--", "--", "--", "--", "--", "--"], ["--", "--", "--", "--", "--", "--", "--", "--"], ["--", "--", "--", "--", "--", "--", "--", "--"], ["wP", "wP", "wP", "wP", "wP", "wP", "wP", "wP"], ["wR", "wN", "wB", "wQ", "wK", "wB", "wN", "wR"] ] self.whiteToMove = True self.moveLog = [] """ Takes a move as a parameter and executes it (this will not work for castling, pawn promotion, and en-passant) """ def makeMove(self, move): self.board[move.startRow][move.startCol] = '--' #clear the square from which the piece has moved self.board[move.endRow][move.endCol] = move.pieceMoved #we've stored what pîece was moved in Move instance before overwrite its square content in Board instance self.moveLog.append(move) #log the move so we can undo it later or to display/get the game's history self.whiteToMove = not self.whiteToMove #swap players """ Undo the last move made """ def undoMove(self): if len(self.moveLog) > 0: #if there is at least 1 move made #get the last move and remove it from move log lastMove = self.moveLog.pop() self.board[lastMove.startRow][lastMove.startCol] = lastMove.pieceMoved self.board[lastMove.endRow][lastMove.endCol] = lastMove.pieceCaptured #put back the eaten piece if so self.whiteToMove = not self.whiteToMove #swap players class Move(): # maps keys to values # key : value ranksToRow = {'1' : 7, '2' : 6, '3' : 5, '4' : 4, '5' : 3, '6' : 2, '7' : 1, '8' : 0} rowsToRanks = {v: k for k, v in ranksToRow.items()} filesToCols = {'a' : 0, 'b' : 1, 'c' : 2, 'd' : 3, 'e' : 4, 'f' : 5, 'g' : 6, 'h' : 7} colsToFiles = {v: k for k, v in filesToCols.items()} def __init__(self, startSq, endSq, board): self.startRow = startSq[0] self.startCol = startSq[1] self.endRow = endSq[0] self.endCol = endSq[1] self.pieceMoved = board[self.startRow][self.startCol] self.pieceCaptured = board[self.endRow][self.endCol] #a special attribute working like a hash to identify a move by a number and allow us to override the "==" operator on Move class self.moveID = self.startRow * 1000 + self.startCol * 100 + self.endRow * 10 + self.endCol """ Overriding the equals method """ def __eq__(self, other): if isinstance(other, Move): return self.moveID == other.moveID return False def getChessNotation(self): # not exactly chess notation but quite close (will be different for pawn moves for example) return self.getRankFile(self.startRow, self.startCol) + self.getRankFile(self.endRow, self.endCol) def getRankFile(self, r, c): return self.colsToFiles[c] + self.rowsToRanks[r] The images Continue reading...