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.
Advertisement
Answer
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.