I'm making a chess game, but I'm completely stuck on the drag and drop element, there's a few guides out there but they're all either dragging shapes, or only dragging one image.
I've tried several variants of code, but all were 50+ lines just to move one .png
and most were incredibly inefficient
pygame.init()
pygame.display.set_caption("Python Chess")
clock = pygame.time.Clock()
red = (213,43,67)
chew = pygame.image.load("chew.png")
gameDisplay.fill(red)
gameDisplay.blit(chew, (400, 400))
pygame.display.update()
drag = 0
if pygame.MOUSEBUTTONDOWN:
drag = 1
if pygame.MOUSEBUTTONUP:
drag = 0
gameExit = False
while not gameExit:
for event in pygame.event.get():
if event.type == pygame.QUIT:
gameExit = True
Image simply doesn't drag.
Introduction to JavaScript Drag and Drop API To drag an image, you simply hold the mouse button down and then move it. To drag the text, you need to highlight some text and drag it in the same way as you would drag an image.
Let's walk through this step by step.
import pygame
def main():
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill(pygame.Color('grey'))
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
We create a window and then start a loop to listen for events and drawing the window.
So far, so good. Nothing to see here, let's move on.
So, we want a chess game. So we need a board. We create a list of lists to represent our board, and we create a Surface
that draws our board on the screen. We want to always seperate our game's state from the actual drawing functions, so we create a board
variable and a board_surf
.
import pygame
TILESIZE = 32
def create_board_surf():
board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
dark = False
for y in range(8):
for x in range(8):
rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(board_surf, pygame.Color('black' if dark else 'white'), rect)
dark = not dark
dark = not dark
return board_surf
def create_board():
board = []
for y in range(8):
board.append([])
for x in range(8):
board[y].append(None)
return board
def main():
screen = pygame.display.set_mode((640, 480))
board = create_board()
board_surf = create_board_surf()
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
screen.fill(pygame.Color('grey'))
screen.blit(board_surf, (0, 0))
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
We need to know which piece we want to select, so we have to translate the screen coordinates (where's the mouse relative to the window?) to the world coordinates (which square of the board is the mouse pointing to?).
So if the board is not located at the origin (the position (0, 0)
), we also have to take this offset into account.
Basically, we have to substract that offset (which is the position of the board on the screen) from the mouse position (so we have the mouse position relative to the board), and divide by the size of the squares.
To see if this works, let's draw a red rectangle on the selected square.
import pygame
TILESIZE = 32
BOARD_POS = (10, 10)
def create_board_surf():
board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
dark = False
for y in range(8):
for x in range(8):
rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(board_surf, pygame.Color('black' if dark else 'white'), rect)
dark = not dark
dark = not dark
return board_surf
def create_board():
board = []
for y in range(8):
board.append([])
for x in range(8):
board[y].append(None)
return board
def get_square_under_mouse(board):
mouse_pos = pygame.Vector2(pygame.mouse.get_pos()) - BOARD_POS
x, y = [int(v // TILESIZE) for v in mouse_pos]
try:
if x >= 0 and y >= 0: return (board[y][x], x, y)
except IndexError: pass
return None, None, None
def main():
screen = pygame.display.set_mode((640, 480))
board = create_board()
board_surf = create_board_surf()
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
piece, x, y = get_square_under_mouse(board)
screen.fill(pygame.Color('grey'))
screen.blit(board_surf, BOARD_POS)
if x != None:
rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(screen, (255, 0, 0, 50), rect, 2)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
Chess is boring without some pieces to move around, so let's create some pieces.
I just use a SysFont
to draw some text instead of using real images, so everyone can just copy/paste the code and run it immediately.
We store a tuple (color, type)
in the nested board
list. Also, let's use some other colors for our board.
import pygame
TILESIZE = 32
BOARD_POS = (10, 10)
def create_board_surf():
board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
dark = False
for y in range(8):
for x in range(8):
rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(board_surf, pygame.Color('darkgrey' if dark else 'beige'), rect)
dark = not dark
dark = not dark
return board_surf
def get_square_under_mouse(board):
mouse_pos = pygame.Vector2(pygame.mouse.get_pos()) - BOARD_POS
x, y = [int(v // TILESIZE) for v in mouse_pos]
try:
if x >= 0 and y >= 0: return (board[y][x], x, y)
except IndexError: pass
return None, None, None
def create_board():
board = []
for y in range(8):
board.append([])
for x in range(8):
board[y].append(None)
for x in range(0, 8):
board[1][x] = ('black', 'pawn')
for x in range(0, 8):
board[6][x] = ('white', 'pawn')
return board
def draw_pieces(screen, board, font):
for y in range(8):
for x in range(8):
piece = board[y][x]
if piece:
color, type = piece
s1 = font.render(type[0], True, pygame.Color(color))
s2 = font.render(type[0], True, pygame.Color('darkgrey'))
pos = pygame.Rect(BOARD_POS[0] + x * TILESIZE+1, BOARD_POS[1] + y * TILESIZE + 1, TILESIZE, TILESIZE)
screen.blit(s2, s2.get_rect(center=pos.center).move(1, 1))
screen.blit(s1, s1.get_rect(center=pos.center))
def draw_selector(screen, piece, x, y):
if piece != None:
rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(screen, (255, 0, 0, 50), rect, 2)
def main():
pygame.init()
font = pygame.font.SysFont('', 32)
screen = pygame.display.set_mode((640, 480))
board = create_board()
board_surf = create_board_surf()
clock = pygame.time.Clock()
while True:
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
piece, x, y = get_square_under_mouse(board)
screen.fill(pygame.Color('grey'))
screen.blit(board_surf, BOARD_POS)
draw_pieces(screen, board, font)
draw_selector(screen, piece, x, y)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
For drag and drop we need two things:
It's actually not that complicated. To enter the "drag-mode", we just set a variable (selected_piece
) when the MOUSEBUTTONDOWN
event occurs. Since we already have the get_square_under_mouse
function, it's easy to know if there's actually a piece under the mouse cursor.
if selected_piece
is set, we draw a line and the piece under the mouse cursor, and we keep track of the current square under the cursor in case the MOUSEBUTTONUP
event occurs. If that's the case, we swap the position of the piece in our board
.
import pygame
TILESIZE = 32
BOARD_POS = (10, 10)
def create_board_surf():
board_surf = pygame.Surface((TILESIZE*8, TILESIZE*8))
dark = False
for y in range(8):
for x in range(8):
rect = pygame.Rect(x*TILESIZE, y*TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(board_surf, pygame.Color('darkgrey' if dark else 'beige'), rect)
dark = not dark
dark = not dark
return board_surf
def get_square_under_mouse(board):
mouse_pos = pygame.Vector2(pygame.mouse.get_pos()) - BOARD_POS
x, y = [int(v // TILESIZE) for v in mouse_pos]
try:
if x >= 0 and y >= 0: return (board[y][x], x, y)
except IndexError: pass
return None, None, None
def create_board():
board = []
for y in range(8):
board.append([])
for x in range(8):
board[y].append(None)
for x in range(0, 8):
board[1][x] = ('black', 'pawn')
for x in range(0, 8):
board[6][x] = ('white', 'pawn')
return board
def draw_pieces(screen, board, font, selected_piece):
sx, sy = None, None
if selected_piece:
piece, sx, sy = selected_piece
for y in range(8):
for x in range(8):
piece = board[y][x]
if piece:
selected = x == sx and y == sy
color, type = piece
s1 = font.render(type[0], True, pygame.Color('red' if selected else color))
s2 = font.render(type[0], True, pygame.Color('darkgrey'))
pos = pygame.Rect(BOARD_POS[0] + x * TILESIZE+1, BOARD_POS[1] + y * TILESIZE + 1, TILESIZE, TILESIZE)
screen.blit(s2, s2.get_rect(center=pos.center).move(1, 1))
screen.blit(s1, s1.get_rect(center=pos.center))
def draw_selector(screen, piece, x, y):
if piece != None:
rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(screen, (255, 0, 0, 50), rect, 2)
def draw_drag(screen, board, selected_piece, font):
if selected_piece:
piece, x, y = get_square_under_mouse(board)
if x != None:
rect = (BOARD_POS[0] + x * TILESIZE, BOARD_POS[1] + y * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.rect(screen, (0, 255, 0, 50), rect, 2)
color, type = selected_piece[0]
s1 = font.render(type[0], True, pygame.Color(color))
s2 = font.render(type[0], True, pygame.Color('darkgrey'))
pos = pygame.Vector2(pygame.mouse.get_pos())
screen.blit(s2, s2.get_rect(center=pos + (1, 1)))
screen.blit(s1, s1.get_rect(center=pos))
selected_rect = pygame.Rect(BOARD_POS[0] + selected_piece[1] * TILESIZE, BOARD_POS[1] + selected_piece[2] * TILESIZE, TILESIZE, TILESIZE)
pygame.draw.line(screen, pygame.Color('red'), selected_rect.center, pos)
return (x, y)
def main():
pygame.init()
font = pygame.font.SysFont('', 32)
screen = pygame.display.set_mode((640, 480))
board = create_board()
board_surf = create_board_surf()
clock = pygame.time.Clock()
selected_piece = None
drop_pos = None
while True:
piece, x, y = get_square_under_mouse(board)
events = pygame.event.get()
for e in events:
if e.type == pygame.QUIT:
return
if e.type == pygame.MOUSEBUTTONDOWN:
if piece != None:
selected_piece = piece, x, y
if e.type == pygame.MOUSEBUTTONUP:
if drop_pos:
piece, old_x, old_y = selected_piece
board[old_y][old_x] = 0
new_x, new_y = drop_pos
board[new_y][new_x] = piece
selected_piece = None
drop_pos = None
screen.fill(pygame.Color('grey'))
screen.blit(board_surf, BOARD_POS)
draw_pieces(screen, board, font, selected_piece)
draw_selector(screen, piece, x, y)
drop_pos = draw_drag(screen, board, selected_piece, font)
pygame.display.flip()
clock.tick(60)
if __name__ == '__main__':
main()
Of course there's a lot that can be improved (like using better datatypes than tuples, extract common logic into functions etc), but this should give you a good start on how to implement such things.
Always keep in mind:
pygame.display.flip
once per frametime.sleep
or pygame.time.wait
Vector2
and Rect
, they'll make your live easier (I didn't the Sprite
class in this code, but it's also very usefull)If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With