Skip to content
Advertisement

How to fix Clock? For Chess. Python

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.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement