Python pyGame - chess piece dragging with no smudges left behind?

Hi

I already asked a dragging question, but didn't ask about not leaving a trail of smudged pixels.
For anyone curious,
This is the stack overflow page i'm using for my Pygame mouse select, mouse-down and drag.

 here

My strategy for smooth dragging over existing pieces is...
On a piece select,
1) paste a blank square over that  piece, to clear its start off space,
2) Remember the x,y click displacement from the top left of the piece square, to help maintain the location of the piece as it is dragged under the cursor.
2) Capture the region under the mouse cursor to be repasted once the piece is dragged to cover up the old piece image. Can I save and re-paste the entire screen, super fast way? Redraw would be 2 slow.
Is the double buffing automatic? SetActivePage? setVisualPage()?

Thanks
LVL 1
beavoidAsked:
Who is Participating?
 
gelonidaCommented:
A more optimized version would be to:
When you start dragging (press the mouse down button)
- empty the field of the figure you clicked on
- make a snapshot of the entire screen.
BUT do not redraw

for every drag step
-  reblit the area of the snapshot screen, that corresponds to the current position of it back to the screen.
- blit the chess piece to it's new position
- update the screen but specify which regions might have changed.

Full code:chess_drag_min_redraw.py
1
 
gelonidaCommented:
It's always easier to help if you could post the minimal code that will show the issue and which can then be adapted by us to get the desired behaviour:


I'm not sure, but could imagine, that your PC is probably performant enough to allow a brute force approach, which would be to redraw everything.

You could try that first in order to see whether you really have to do any further effort.

No let's assume you find out, that brute force is not good enough or that you just for the fun of it you do not want to waste that much CPU power
or that you don't want to waste CPU time as you want it for your chess AI:

1.) create one function, that you call just once at the beginning of the game:
This function will draw your chessboard at the beginning of the game without any figures.
after drawing, just save this as your background image.

self.background =screen.copy()

Open in new window


2.) Now draw your figures on the board:

3.) render the board with
pygame.display.flip()

Open in new window


4.) Now if you want to start dragging:
remove the figure that you want to drag from the display
and save the screen
pre_drag_screen = screen.copy()

Open in new window

now draw the figure at the given position on the screen.
if the mouse position changed. undraw the figure by blitting the pixles from the specificregionfrom pre_drag_screen onto the current screen
and redraw the figure in the new position.

Will try to send some code later.
1
 
gelonidaCommented:
I made a first test:

Drawing everything every time would probably be fast enough.

So the simplistic solution could be:


one function to draw an empty board:

    def draw_empty_board(self):
        screen = self.screen
        size = FIELD_WIDTH
        screen.fill(MYBLACK)
        for x in range(8):
            for y in range(x % 2, 8, 2):
                pygame.draw.rect(screen, MYWHITE, (x*size,y*size,size,size), 0)

Open in new window


One function to draw the board with all its figures:
    def draw(self):
        """ draw board with current figures """
        self.draw_empty_board()
        for position, figure in self.figures.items():
            x, y = position
            x *= FIELD_WIDTH
            y *= FIELD_WIDTH
            self.screen.blit(figure, (x, y))

Open in new window


and the main loop code which always draws the full board, removes (draws over)  the figure to be dragged and redraws it the current mouse offset:
    def run(self):
        self.init_pygame()
        clock = self.clock
        field_from = None 
        board = self.board
        figures = self.figures
        width = 0
        while self.running:
            clock.tick(30)
            for event in pygame.event.get():
                if event.type == pygame.MOUSEBUTTONDOWN:
                    if event.button == 1:
                        x, y = event.pos
                        x //= FIELD_WIDTH
                        y //= FIELD_WIDTH 
                        print("X", x, "Y", y)
                        field = (x, y)
                        if board.figure_on_field(field):
                            width = (width % FIELD_WIDTH) + 10
                            self.dragging = True # so we want to drag this figure
                            print("clicked on a figure", field)
                            px, py = self.drag_start = event.pos # required to get the relative move position
                            self.field_from = field
                        else:
                            self.dragging = False
                elif event.type == pygame.MOUSEMOTION and self.dragging: # are we dragging?
                    self.draw() # draw the current board
                    x, y = field = self.field_from
                    tlx, tly = (x *FIELD_WIDTH, y * FIELD_WIDTH)
                    # remove the piece to be dragged
                    field_color = MYWHITE if (x+y) % 2 == 0 else MYBLACK
                    pygame.draw.rect(self.screen, field_color, (tlx, tly, FIELD_WIDTH, FIELD_WIDTH), 0)
                    figure = self.figures[(x, y)]
                    new_x = tlx + event.pos[0] - self.drag_start[0]
                    new_y = tly + event.pos[1] - self.drag_start[1]
                    # draw the piece at the newposition
                    self.screen.blit(figure, (new_x, new_y))
                    pygame.display.update()
                elif event.type == pygame.MOUSEBUTTONUP:
                    if event.button == 1 and self.dragging:
                        print("UP")
                        self.dragging = False  # add code here to actually move the piece
                        x, y = field = self.field_from
                        x = int((event.pos[0] - self.drag_start[0]) / FIELD_WIDTH + x + 0.5)
                        y = int((event.pos[1] - self.drag_start[1]) / FIELD_WIDTH + y + 0.5)
                        print("MOVE FROM %r to %r" % (self.field_from, (x, y)))
                        if board.is_valid_move(self.field_from, (x, y)):
                            self.move(self.field_from, (x, y))
                            self.draw() # draw the board after the move
                            pygame.display.update()
                elif event.type == pygame.KEYDOWN:
                    if event.unicode == 'q':
                        self.running = False
        pygame.quit()

Open in new window



Here the full example chess_drag_full_redraw.py
0
Cloud Class® Course: Certified Penetration Testing

This CPTE Certified Penetration Testing Engineer course covers everything you need to know about becoming a Certified Penetration Testing Engineer. Career Path: Professional roles include Ethical Hackers, Security Consultants, System Administrators, and Chief Security Officers.

 
beavoidAuthor Commented:
Thanks, I might do a clever redraw of the board squares and pieces under the area where the mouse was,
but is there a way to store a part of the screen
In GWbasic, there was putimage for blitting and getimage for storing part of the screen.

Can Python do that?
0
 
gelonidaCommented:
look at chess_drag_min_redraw.py:

I think for the current use case you just copy the entire screen only once, when you press the down button as you could potentially drag your figure over the entire screen.

So screen.copy() copies the whole screen()
to emulate a getimage you might create a subsurface and then copy it:
copy = screen.subsurface(Rect).copy()

Open in new window

This will however only work if the screen is not hardfware accelerated.
If you really need this functionality then probably create a surfice with the required size and blit from the screen to the surface.

the equivalent of putimage is screen.blit(yourimage, position)

Open in new window


As the huge copy is only done once per drag (on mouse button down) and all the rest of my code just blits small recangles I don't think you'll get a measurable performance boost with your suggested approach. You might even be slower as you want to create new small snapshots for every mouse movewhereas my suggestion just creates one huge snaphost at the start. and just blits one rectangle to the screen where the figure was (restores the state before dragging started) and blits the piece to it's new position.

did you try the code?
1
 
beavoidAuthor Commented:
Thanks
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.