Search code examples
pythontimerkivykivy-language

How to create a lap function for a countdown timer in Kivy


I have a countdown timer that starts at a random integer number within the range given by the randint() arguments and counts down to zero. The aim is to make the timer restart at a new random number the first time it reaches zero (i.e. lap function) and to display "FINISH" the second time it reaches zero.

This is my first time using kivy, so apologies if the solution is obvious. At present I only require two iterations, but I may need to adjust this later so that the timer will lap any number of times before finally stopping. The number of laps will be determined in the code before running the app, and not by the app user whilst running the app.

from kivy.app import App
from kivy.uix.label import Label
from kivy.animation import Animation
from kivy.properties import NumericProperty
from random import randint


class IncrediblyCrudeClock(Label):
    for i in range(2):
        r=randint(3,7)
        a = NumericProperty(r)  # Number of seconds to countdown


        def start(self):  #Function to initiate the countdown
            Animation.cancel_all(self)  # stop any current animations
            self.anim = Animation(a=0, duration=self.a)  #a=0 sets the 
#final destination of a. duration sets the time taken to reach stopping 
#point (i.e 5 seconds for a=5)
            def finish_callback(animation, incr_crude_clock):
                if self.i==1:
                    incr_crude_clock.text = "FINISHED"  #when the clock 
#reaches zero, display "FINISHED"
            self.anim.bind(on_complete=finish_callback)  #call the 
#finish_callback function once a=0
            self.anim.start(self)  #Start the animation (otherwise clock 
#stuck at 5 for a=5)


class TimeApp(App):
    def build(self):
        crudeclock = IncrediblyCrudeClock()
        crudeclock.start()
        return crudeclock

if __name__ == "__main__":
    TimeApp().run()



<IncrediblyCrudeClock>
    text: str(round(self.a, 1))

The app does run as expected for the first countdown. A random number is selected and the timer counts down to zero, but it stops and displays "FINISHED" after the first count down. It seems as though the for loop is iterating from zero to one before the app is actually started, so, by the time the countdown starts, i is already equal to 1 (in stead of running first from a to zero with i=0 and then from new a to zero with i=1). I imagine this is because the for loop is in the wrong place (i.e. not upon calling the start function), but I have been unable to work out how to rectify this. This is also my first time using stack overflow, so please let me know if you need to know anything else.


Solution

  • Here is a version that repeats the countdown a specified number of times:

    from random import randint
    from kivy.animation import Animation
    from kivy.app import App
    from kivy.lang import Builder
    from kivy.properties import NumericProperty
    from kivy.uix.label import Label
    
    
    class IncrediblyCrudeClock(Label):
        a = NumericProperty(0)  # Number of seconds to countdown
    
        def __init__(self, **kwargs):
            self.max_laps = kwargs.pop('laps', 2)  # default is to do 2 laps
            self.lap_counter = 0
            super(IncrediblyCrudeClock, self).__init__(**kwargs)
    
        def start(self, *args):
            self.lap_counter += 1
            self.a = randint(3, 7)
            self.anim = Animation(a=0, duration=self.a)
            if self.lap_counter >= self.max_laps:
                # this is the last lap, set on_complete to call self.finish_callback
                self.anim.bind(on_complete=self.finish_callback)
            else:
                # not finished yet, call self.start again
                self.anim.bind(on_complete=self.start)
            print('starting anim number', self.lap_counter)
            self.anim.start(self)
    
        def finish_callback(self, animation, incr_crude_clock):
            print('in finish_callback')
            self.text = 'FINISHED'
    
    Builder.load_string('''
    <IncrediblyCrudeClock>
        text: str(round(self.a, 1))
    ''')
    
    
    class TimeApp(App):
        def build(self):
            # specify the number of repetitions in the constructor
            crudeclock = IncrediblyCrudeClock(laps=3)
            crudeclock.start()
            return crudeclock
    
    if __name__ == "__main__":
        TimeApp().run()