Related
I have created a memory card matching game where a player has to match up 30 cards on the screen. There is a timer on the screen which goes up by 1 every second. I want it so once the player has matched up all 15 pairs, the game ends and the players time, so how long they took to complete it is displayed in another screen which says - "Your Time was ..." .
So far i have coded everything , i have a timer that works perfectly fine and all the matching aspects is good. However, once the game ends and the text is displayed on the final winning screen, the Timer is still going and so i cant get the players final time. For example, if the player took 60 seconds to complete all pairs it should say "Your Time was 60 seconds!!". However even after the game is ended the timer is still going. Here is my code so far:
# build a guessing game!
import random
import pygame
global options_list, spaces, used, new_board, first_guess, second_guess, first_guess_num, second_guess_num, score, matches, game_over, rows, cols, correct, time
pygame.init()
# game variables and constants
screen_width = 600
screen_height = 600
white = (255, 255, 255)
black = (0, 0, 0)
orange = (255,97,3)
turquoise = (0, 206, 209)
green = (0, 255, 0)
fps = 60
timer = pygame.time.Clock()
rows = 5
cols = 6
correct = [[0, 0, 0, 0, 0, 0,],
[0, 0, 0, 0, 0, 0,],
[0, 0, 0, 0, 0, 0,],
[0, 0, 0, 0, 0, 0,],
[0, 0, 0, 0, 0, 0]]
options_list = []
spaces = []
used = []
new_board = True
first_guess = False
second_guess = False
#Guess is assigned an index value. Checks what number is clicked
first_guess_num = 0
second_guess_num = 0
score = 0
matches = 0
game_over = False
cards_left_covered = 0
#timer start time
start_time = pygame.time.get_ticks()
time = 0
# create screen
screen = pygame.display.set_mode([screen_width, screen_height])
pygame.display.set_caption('Memory Game!')
#sets fonts
large_font = pygame.font.Font('freesansbold.ttf', 56)
small_font = pygame.font.Font('freesansbold.ttf', 26)
def generate_board():
global options_list, spaces, used
for item in range(rows * cols // 2):
options_list.append(item)
# goes through list of options between 1-24. Guarantees that only 2 cards can be selected
for item in range(rows * cols):
card = options_list[random.randint(0, len(options_list) - 1)]
spaces.append(card)
if card in used:
used.remove(card)
options_list.remove(card)
else:
used.append(card)
#Sets background colours and shapes
def draw_backgrounds():
global start_time, time
top_menu = pygame.draw.rect(screen, orange, [0, 0, screen_width, 75])
score_text = small_font.render(f'Player 1 score : {score}', True, white)
screen.blit(score_text, (20, 20))
board_space = pygame.draw.rect(screen, turquoise, [0, 100, screen_width, screen_height - 200], 0)
bottom_menu = pygame.draw.rect(screen, orange, [0, screen_height - 100, screen_width, 100], 0)
time = 0 + int((pygame.time.get_ticks() + start_time) / 1000)
time_text = small_font.render(f'Your time: {time}', True, white)
screen.blit(time_text, (20, 550))
def draw_cards():
global rows, columns, correct
card_list = []
for i in range(cols):
for j in range(rows):
#draws the cards and sets their size and position
card = pygame.draw.rect(screen, orange,[i * 85 + 48, j *78 + 110, 61, 65], 0, 4)
card_list.append(card)
## randomly adds numbers onto the cards. to make sure that the black numbers dont populate instantly when game is created
'''card_text = small_font.render(f'{spaces[i * rows + j]}', True, black)
screen.blit(card_text, (i * 75 + 18, j * 65 + 120))'''
for r in range(rows):
for c in range(cols):
if correct[r][c] == 1:
#creates green border around cards when match is made
pygame.draw.rect(screen, green, [c * 85 + 48, r * 78 + 110, 61, 65], 3, 4)
card_text = small_font.render(f'{spaces[c * rows + r]}', True, black)
screen.blit(card_text, (c * 85 + 55, r * 78 + 125))
return card_list
def check_guesses(first, second):
global spaces, correct, score, matches
if spaces[first] == spaces[second]:
#floor division
col1 = first // rows
col2 = second // rows
row1 = first - (first // rows * rows)
row2 = second - (second // rows * rows)
#checks for match and score incremented by 1
if correct[row1][col1] == 0 and correct[row2][col2] == 0:
correct[row1][col1] = 1
correct[row2][col2] = 1
score += 1
matches += 1
running = True
while running:
timer.tick(fps)
screen.fill(turquoise)
if new_board:
generate_board()
new_board = False
draw_backgrounds()
board = draw_cards()
if first_guess and second_guess:
check_guesses(first_guess_num, second_guess_num)
##delays code for miliseconds to see second guess
pygame.time.delay(1000)
first_guess = False
second_guess = False
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
if event.type == pygame.MOUSEBUTTONDOWN:
for i in range(len(board)):
button = board[i]
#can guess as long as it's not game over
if not game_over:
if button.collidepoint(event.pos) and not first_guess:
first_guess = True
first_guess_num = i
##ensures that the same card cannot be clicked twice
if button.collidepoint(event.pos) and not second_guess and first_guess and i != first_guess_num:
second_guess = True
second_guess_num = i
#Checks for game over
if matches == rows * cols // 2:
game_over = True
winner = pygame.draw.rect(screen, turquoise,[0, 0, 600, 600])
winner_text = large_font.render(f'YOUR TIME WAS {end_time} !!', True, orange)
screen.blit(winner_text, (70, screen_height - 350))
#allows card to be flipped to show number
if first_guess:
card_text = small_font.render(f'{spaces[first_guess_num]}', True, black)
location = (first_guess_num // rows * 85 + 55, (first_guess_num - (first_guess_num // rows * rows)) * 78 + 125)
screen.blit(card_text, (location))
if second_guess:
card_text = small_font.render(f'{spaces[second_guess_num]}', True, black)
location = (second_guess_num // rows * 85 + 55, (second_guess_num - (second_guess_num // rows * rows)) * 78 + 125)
screen.blit(card_text, (location))
pygame.display.flip()
pygame.quit()
Just set end_time when the game state changes to game_over:
while running:
# [...]
if matches == rows * cols // 2:
if not game_over:
end_time = time
game_over = True
winner = pygame.draw.rect(screen, turquoise,[0, 0, 600, 600])
winner_text = large_font.render(f'YOUR TIME WAS {end_time} !!', True, orange)
screen.blit(winner_text, (70, screen_height - 350))
I want 'rock' to be able to automatically move to the left when running the program but nothing happens, from my understanding I have made it so that the rocks x position moves by 3 every iteration
import pygame
p = pygame.display.set_mode((900, 600))
pygame.display.set_caption("First game")
FPS = 60
WHITE = 255, 255, 255
RED = 255, 0, 0
GREEN = 0, 255, 0
BLUE = 0, 0, 255
DBLUE = 57, 64, 90
board1 = pygame.image.load("board.png")
board2 = pygame.image.load("board.png")
rock = pygame.image.load("rock.png")
def draw_window(board1_move, board2_move, rock_scaled):
p.fill(DBLUE)
board1_scaled = pygame.transform.rotate(pygame.transform.scale(board1, (55, 40)), 40)
board2_scaled = pygame.transform.rotate(pygame.transform.scale(board2, (55, 40)), 40)
rock_scaled = pygame.transform.rotate(pygame.transform.scale(rock, (55, 40)), 0)
p.blit(board1_scaled, (board1_move.x, board1_move.y))
p.blit(board2_scaled, (board2_move.x, board2_move.y))
p.blit(rock_scaled, (400, 250))
pygame.display.update()
keypress = pygame.key.get_pressed()
def board1_move_func(keypress, board1_move):
if keypress[pygame.K_w] and board1_move.y > 0:
board1_move.y -= 3
if keypress[pygame.K_s] and board1_move.y < 530:
board1_move.y += 3
def board2_move_func(keypress, board2_move):
if keypress[pygame.K_UP] and board2_move.y > 0:
board2_move.y -= 3
if keypress[pygame.K_DOWN] and board2_move.y < 530:
board2_move.y += 3
def main():
rock_x = 450
rock_y = 250
board1_move = pygame.Rect(20, 250, 55, 40)
board2_move = pygame.Rect(805, 250, 55, 40)
rock_move = pygame.Rect(rock_x, rock_y, 55, 40)
clock = pygame.time.Clock()
run = True
while run == True:
clock.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
keypress = pygame.key.get_pressed()
rock_x += 3
board1_move_func(keypress, board1_move)
board2_move_func(keypress, board2_move)
draw_window(board1_move, board2_move, rock_move)
pygame.quit()
if __name__ == "__main__":
main()
You change rock_x, but draw the object at the position stored in rock_move. rock_move is not magically tied with rock_x. You can update rock_move after changing rock_x:
rock_x += 3
rock_move.x = rock_x
However, I recommend changing rock_move instead of rock_x:
rock_move.x += 3
I'm creating a Chess clock for my chess project. In chess there are time formats 5|5 is where each player (white and black) is granted a general 5 minutes, so when either player makes a move it adds 5 seconds to the clock. So 15|10 is 15 minutes in general and 10 seconds per move.
My code:
import pygame
import time
pygame.init()
size = width, height = 300, 300
screen = pygame.display.set_mode(size)
# Fonts
Font = pygame.font.SysFont("Trebuchet MS", 25)
class Button:
def __init__(self, color, x, y, width, height, text=''):
self.color = color
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
def draw(self, screen, outline=None):
# Call this method to draw the button on the screen
if outline:
pygame.draw.rect(screen, outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height), 0)
if self.text != '':
font = pygame.font.SysFont('freesansbold.ttf', 16)
text = font.render(self.text, 1, (0, 0, 0))
screen.blit(text, (
self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2)))
def setColor(self, new_color):
self.color = new_color
def isOver(self, pos):
# Pos is the mouse position or a tuple of (x,y) coordinates
if self.x < pos[0] < self.x + self.width:
if self.y < pos[1] < self.y + self.height:
return True
return False
def main():
global Second, Minute
running = True
Black = 0, 0, 0
White = 255, 255, 255
Second = 0
Minute = 0
five_five = False
Clock = pygame.time.Clock()
while running:
five_five_button = Button(White, 150, 150, 40, 20, '5|5')
five_plus_five = Button(White, 150, 180, 40, 20, "+5 secs")
five_five_button.draw(screen, None)
five_plus_five.draw(screen, None)
pygame.display.update()
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
if e.type == pygame.MOUSEBUTTONDOWN:
if five_five_button.isOver(e.pos):
five_five = True
if five_plus_five.isOver(e.pos):
Second = +5
if e.type == pygame.KEYDOWN:
pass # if you want to add keys to test things easier
if five_five:
Minute = 5
Second = 0
five_five = False
if not five_five:
if Second == 0:
Second = 58
Minute = Minute - 1
Second += 2
time.sleep(1)
Second -= 1
# Minute
MinuteFont = Font.render(str(Minute).zfill(2), True, Black)
MinuteFontR = MinuteFont.get_rect()
MinuteFontR.center = (50, 60)
# Second
SecondFont = Font.render(":" + str(Second).zfill(2), True, Black)
SecondFontR = SecondFont.get_rect()
SecondFontR.center = (80, 60)
screen.fill(White)
# Timer
# while Time==0: this will cause a crash !
screen.blit(SecondFont, SecondFontR)
screen.blit(MinuteFont, MinuteFontR)
pygame.display.flip()
Clock.tick(60)
if __name__ == '__main__':
main()
The issues I face:
So let's say the player makes a move and the time is at 4:59. We want to add 5 secs. So it adds 5 seconds so 4:64 so it'll minus a minute so 3:04. I don't know how to fix that.
Another is that whenever you want to add 5 seconds or whatever it'll never goes past 59 and add those seconds on to the total and add a minute.
I've worked hours on this believe it or not figuring out the simple things. I've looked up how to make clocks and things in python (or pygame) and I always get adding clocks. I never found a clock that adds time, but also counts down at the same time.
Whenever I even try to load or click one of the buttons. It takes a few seconds to register it. So if you can help me figure this all out or find out a better way to make it I appreciate it a lot.
Name changes
First of all, I would rename some variables to better convey their meaning (given that some weren't immediately obvious), so I changed these:
five_five_button -> reset_button
five_plus_five -> add_button
Second -> seconds
Minute -> minutes
Black -> BLACK
White -> WHITE
Button creation
Next, I don't think you need to re-create your two buttons every frame. Just once should be enough to enable them to be drawn, so I moved them as shown:
# moved to here
reset_button = Button(White, 150, 150, 40, 20, '5|5')
add_button = Button(White, 150, 180, 40, 20, "+5 secs")
while running:
# removed from here
# reset_button = Button(White, 150, 150, 40, 20, '5|5')
# add_button = Button(White, 150, 180, 40, 20, "+5 secs")
# ... rest of loop code ...
Minutes and seconds timer
Next, I like what you were going for with the reset and adding time code, but think in a scenario like this it actually just makes things more complicated. So I removed these if statements:
if five_five:
minutes = 5
seconds = 0
five_five = False
if not five_five:
if seconds == 0:
seconds = 58
minutes = minutes - 1
seconds += 2
and moved their logic straight into the pygame event handling loop code:
if e.type == pygame.MOUSEBUTTONDOWN:
if reset_button.isOver(e.pos):
minutes = 5
seconds = 0
if add_button.isOver(e.pos):
seconds += 5
but this also means that we don't have any logic to handle what happens if the seconds goes "out of bounds", so I wrote the following to happen right after our pygame event loop:
while seconds >= 60:
seconds -= 60
minutes += 1
while seconds < 0:
seconds += 60
minutes -= 1
Splitting into functions
The refactored code is almost complete, but before that I want to split our code into functions to help maintainability and modularilty. I also really like the use of classes for the buttons.
First I want a "draw everything on the screen" function:
def draw_screen():
global WHITE, BLACK
screen.fill(WHITE)
# buttons
reset_button.draw(screen, None)
add_button.draw(screen, None)
# minutes tezt
minutesFont = Font.render(str(minutes).zfill(2), True, BLACK)
minutesFontR = minutesFont.get_rect()
minutesFontR.center = (50, 60)
screen.blit(minutesFont, minutesFontR)
# seconds text
secondsFont = Font.render(":" + str(seconds).zfill(2), True, BLACK)
secondsFontR = secondsFont.get_rect()
secondsFontR.center = (80, 60)
screen.blit(secondsFont, secondsFontR)
pygame.display.flip()
Timer
The reason you feel that the buttons take forever to respond is because pygame can only respond to button presses once a second. This is because of your time.sleep(1), meaning that your event loop is only run once a second. We will need a non-blocking way of decreasing the seconds timer by 1 once a second:
previousTime = time.time()
# ...
while running:
# ...
if time.time() - previousTime > 1:
previousTime = time.time()
seconds -= 1
Final code
import pygame
import time
pygame.init()
size = width, height = 300, 300
screen = pygame.display.set_mode(size)
# Fonts
Font = pygame.font.SysFont("Trebuchet MS", 25)
class Button:
def __init__(self, color, x, y, width, height, text=''):
self.color = color
self.x = x
self.y = y
self.width = width
self.height = height
self.text = text
def draw(self, screen, outline=None):
# Call this method to draw the button on the screen
if outline:
pygame.draw.rect(screen, outline, (self.x - 2, self.y - 2, self.width + 4, self.height + 4), 0)
pygame.draw.rect(screen, self.color, (self.x, self.y, self.width, self.height), 0)
if self.text != '':
font = pygame.font.SysFont('freesansbold.ttf', 16)
text = font.render(self.text, 1, (0, 0, 0))
screen.blit(text, (
self.x + (self.width / 2 - text.get_width() / 2), self.y + (self.height / 2 - text.get_height() / 2)))
def setColor(self, new_color):
self.color = new_color
def isOver(self, pos):
# Pos is the mouse position or a tuple of (x,y) coordinates
if self.x < pos[0] < self.x + self.width:
if self.y < pos[1] < self.y + self.height:
return True
return False
def draw_screen(minutes, seconds):
global WHITE, BLACK
screen.fill(WHITE)
# buttons
reset_button.draw(screen, None)
add_button.draw(screen, None)
# minutes tezt
minutesFont = Font.render(str(minutes).zfill(2), True, BLACK)
minutesFontR = minutesFont.get_rect()
minutesFontR.center = (50, 60)
screen.blit(minutesFont, minutesFontR)
# seconds text
secondsFont = Font.render(":" + str(seconds).zfill(2), True, BLACK)
secondsFontR = secondsFont.get_rect()
secondsFontR.center = (80, 60)
screen.blit(secondsFont, secondsFontR)
pygame.display.flip()
def fix_time(minutes, seconds):
while seconds >= 60:
seconds -= 60
minutes += 1
while seconds < 0:
seconds += 60
minutes -= 1
return minutes, seconds
def handle_events(minutes, seconds, reset_button, add_button):
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
if e.type == pygame.MOUSEBUTTONDOWN:
if reset_button.isOver(e.pos):
minutes = 5
seconds = 0
if add_button.isOver(e.pos):
seconds += 5
return minutes, seconds
def main():
global seconds, minutes
running = True
BLACK = 0, 0, 0
WHITE = 255, 255, 255
seconds = 0
minutes = 0
previousTime = time.time()
Clock = pygame.time.Clock()
reset_button = Button(WHITE, 150, 150, 40, 20, '5|5')
add_button = Button(WHITE, 150, 180, 40, 20, "+5 secs")
while running:
minutes, seconds = handle_events(minutes, seconds, reset_button, add_button)
if time.time() - previousTime > 1:
previousTime = time.time()
seconds -= 1
minutes, seconds = fix_time(minutes, seconds)
draw_screen(minutes, seconds):
Clock.tick(60)
if __name__ == '__main__':
main()
Thoughts
I haven't run any of the code, but the thought, intent and improvement is still just as valid. There are still improvements that could be made to the code (having to return minutes and seconds, and passing reset_button and add_button as parameters, are both "not great") so feel free to use my example as inspiration.
So let's just rebuild the block where you handle minute changes.
Things we need to implement:
Every tick, needs to check if seconds has gone over 59 or under 0
If over, then we need to add a minute
If under, then we need to subtract a minute
In both cases, we need to reset the seconds
We can do all that with simple if statements. Have a look at the following.
I am also going to move the seconds tick to be before the check, to make our math a bit more clear (Change the time, and then fix it)
Second -= 1
if not five_five:
if Second < 0:
Minute -= 1
Second += 60 # Since Seconds will probably be -1
elif Second > 59:
Minute += 1
Second -= 60
Here's the code, i'm working on Atom. I've been using pygame for a lot of time, and that rarely happens. I never knew what the problem was, but i never needed to, until now.
import pygame
from random import randint as rnd
from colorama import Cursor
import math
import os
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (600,90)
pygame.init()
xsize, ysize = 700, 700
screen = pygame.display.set_mode((xsize, ysize))
screen.fill((0, 0, 0))
def dis(p1, a1):
return math.sqrt((p1[0] - a1[0])**2 + (p1[1] - a1[1])**2)
inner = 0
tot = 0
pygame.draw.circle(screen, (255, 255, 255), (350, 350), 350, 2)
while True:
pos = (rnd(0, xsize), rnd(0, ysize))
if dis((350, 350), pos) < 350:
color = (50, 255, 50)
inner += 1
else:
color = (50, 50, 250)
tot += 1
pygame.draw.circle(screen, color, pos, 2)
print(" pi = " + str(4 * inner / tot) + "\nnÂș dots: " + str(tot), Cursor.UP(2))
pygame.display.flip()
The window freeze, because you do not handle the events. You have to handle the events by either pygame.event.pump() or pygame.event.get(), to keep the window responding.
Add an event loop, for instance:
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
# [...]
I am having a problem where i am trying to get a circle to go to the same spot every time i execute the program. But each time I run the code, the dot doesn't always line up. I have a test circle in the same place to compare run to run. The Red circle should cover the white circle perfectly but it changes every time i run the program. I am reseting the kernal as i am using pygame.time.get_ticks() to time everything.
import sys, pygame, math
from pygame.locals import *
# set up a bunch of constants
BLUE = ( 0, 0, 255)
WHITE = (255, 255, 255)
ORANGE = (255, 165, 0)
PINK = (255, 20, 147)
RED = (255, 0, 0)
GREEN = ( 0, 255, 0)
LIMEGREEN = ( 50, 205, 50)
YELLOW = (255, 255, 0)
PURPLE = (160, 32, 240)
BLACK = ( 0, 0, 0)
#Background Colour
BGCOLOR = BLACK
#Setting Window Size and finding window x and y centre
WINDOWWIDTH = 1918# width of the program's window, in pixels 960x540
WINDOWHEIGHT = 1078# height in pixels
WIN_CENTERX = int(WINDOWWIDTH / 2) # the midpoint for the width of the window
WIN_CENTERY = int(WINDOWHEIGHT / 2) # the midpoint for the height of the window
# frames per second to run at
FPS = 60
#intializing Variables
AMPLITUDE = 450
colourArray=[BLUE,WHITE,YELLOW,GREEN,RED,PINK,PURPLE,LIMEGREEN,ORANGE]
i=0
xPos = 0
step = 0
small_step =0
stop_step=step=0
xPos=0
yPos=0
c=RED
timestep=0
# standard pygame setup code
pygame.init()
FPSCLOCK = pygame.time.Clock()
DISPLAYSURF = pygame.display.set_mode((WINDOWWIDTH, WINDOWHEIGHT),pygame.FULLSCREEN)
pygame.display.set_caption('Task1')
fontObj = pygame.font.Font('freesansbold.ttf', 16)
# main application loop
while True:
# event handling loop for quit events
for event in pygame.event.get():
if event.type == QUIT or (event.type == KEYUP and event.key == K_ESCAPE):
pygame.quit()
sys.exit()
#setup for label and time
tempTime=pygame.time.get_ticks()/1000
time_string=str(tempTime)
instructionsSurf = fontObj.render(time_string, True, WHITE, BGCOLOR)
instructionsRect = instructionsSurf.get_rect()
instructionsRect.left = 10
instructionsRect.bottom = WINDOWHEIGHT - 10
# fill the screen to draw from a blank state
DISPLAYSURF.fill(BGCOLOR)
DISPLAYSURF.blit(instructionsSurf, instructionsRect)
tempTime=pygame.time.get_ticks()/1000
#Color change loop
c=RED
if (0<=(tempTime)<3):
c=RED
if (3<=(tempTime)<5):
c=BLUE
if (5<=(tempTime)<7):
c=GREEN
if (7<=(tempTime)<9):
c=YELLOW
if (9<=(tempTime)<11):
c=WHITE
if (11<=(tempTime)<17):
c=RED
if (17<=(tempTime)<42):
c=RED
if (42<=(tempTime)<46):
c=RED
if (46<=(tempTime)<120):
c=colourArray[i]
#Setting position of x and y coordinates
if (0<=(tempTime)<14):
xPos = 0
yPos = 0
if (14<(tempTime)<17):
small_step += 5.111
xPos = small_step
yPos = 0
if (17<(tempTime)<43):
step += 0.05001
step %= 2 * math.pi
xPos = math.cos(step) * AMPLITUDE
yPos = math.sin(step) * AMPLITUDE
if (43<(tempTime)<46):
stop_step=step
xPos = math.cos(stop_step) * AMPLITUDE
yPos = math.sin(stop_step) * AMPLITUDE
if (46<(tempTime)<120):
step += 0.05001
step %= 2 * math.pi
xPos = math.cos(step) * AMPLITUDE
yPos = math.sin(step) * AMPLITUDE
#test dot
pygame.draw.circle(DISPLAYSURF, WHITE, (WIN_CENTERX+AMPLITUDE, 0+WIN_CENTERY),12,0)
# draw dot1
dot1=pygame.draw.circle(DISPLAYSURF, c, (int(xPos)+ WIN_CENTERX, int(yPos) + WIN_CENTERY), 12,0)
# draw dot2
dot2=pygame.draw.circle(DISPLAYSURF, BLACK, (int(xPos) + WIN_CENTERX, int(yPos) + WIN_CENTERY), 6,0)
#refresh
pygame.draw.rect(DISPLAYSURF, BLACK, (0, 0, WINDOWWIDTH, WINDOWHEIGHT), 1)
pygame.display.update()
FPSCLOCK.tick(FPS)
I've only scanned your code but I would guess your inconsistency is due to a high frame rate (60). FPSCLOCK.tick(FPS) will make sure you go up to 60, but does not mean you will go 60 fps. So if your computer can not handle 60 frames per second, it will go below 60 frames.
Cristph Terasa's recommendation of using busy_loop should do the job for ya, but I personally have no experience for it and want to share a method of normalizing game speed across different FPSs.
Rather than reinventing a wheel, here's a link to a question that explains it. I recommend the second answer, written by pmoleri.
In Pygame, normalizing game-speed across different fps values
This solution should help your game run at the same speed no matter the frame rate.