1

I have a button that executes a function (in tkinter). Let's say it adds money in a video game. So when you press that button, you get 100 dollars. However, I only want my user to be able to add 100 dollars once per minute. Is there a way to only allow a function to be called once a minute? (maybe with the time module or something?)

The most i know about managing time in a function is this, I would like to do something along these lines:

def add_money():
money = money + 100
app.after(5000, add_money)

I know that this function would obviously not get what i want. Is there a way to implement it so that if you clicked the button and a minute had not passed, the program would do nothing?

Thanks in advance

codeforfood
  • 429
  • 9
  • 28
  • possible duplicate of [Code a timer in a python GUI in TKinter](http://stackoverflow.com/questions/2400262/code-a-timer-in-a-python-gui-in-tkinter) and all the others one gets with search for `tkinter` and `timer`. – tom10 Jul 26 '13 at 19:13
  • I don't think this is a duplicate of the timer options. This question is about how to disable functionality for a period of time. While that is done using the same mechanism as a timer, it's different enough to be worth keeping open, IMO. – Bryan Oakley Aug 28 '13 at 22:28

3 Answers3

1

Just track time yourself, and do nothing if too little time has elapsed.

def add_money():
    curr_time = int(time.time())
    if curr_time - app.last_time > 60:
        app.last_time = curr_time
        money = money + 100

This way, the function can be called as often as the user likes, but it will only do what he wants once a minute.

chepner
  • 497,756
  • 71
  • 530
  • 681
1

I'd actually likely implement this as a decorator as it sounds like something reusable. The sample below allows you to reuse guarded on any function you want, specifying the timeout (in seconds) for each function you use the decorator on:

import time
from functools import wraps

_guards = {}

def guarded(tm):
    def wrapper(fn):
        _guards[fn] = {'timeout': tm} 
        @wraps(fn)
        def inner(*args, **kwargs):
            t = time.time()
            if 'last' not in _guards[fn] or \
                t - _guards[fn]['last'] > _guards[fn]['timeout']:
                _guards[fn]['last'] = t 
                fn(*args, **kwargs)

        return inner
    return wrapper

money = 0 
@guarded(2)
def add_money():
    global money
    money += 100 
    print money

Here, add_money is only allowed after a 2 second timeout:

In [2]: add_money()
100
In [3]: add_money()
In [4]: add_money()
200
In [5]: add_money()
In [6]: add_money()
In [7]: add_money()
In [8]: add_money()
300

Yes, there are arguments for not using a global dict to track this stuff (bad when running multithreaded) and to use threading.local instead, but that's kinda outside the scope of this answer.

Demian Brecht
  • 21,135
  • 5
  • 42
  • 46
  • while this is functional, from a user experience point of view it's not a good solution. In addition to disallowing the function, the UI should give some sort of feedback -- either disable the button or show a message saying the action is disallowed, etc. So, while the decorator is interesting, I think it might be difficult to use (or re-use) if you want to do some UI actions when then function is disabled. – Bryan Oakley Aug 28 '13 at 22:30
1

In the add_money function, disable the "add money" button, and then schedule it to be re-enabled after one minute:

def add_money():
    <do the real work here>

    add_money_button.configure(state="disabled")
    root.after(60000, lambda: add_money_button.configure(state="normal"))

If your app has multiple ways to add money, such as a menu item, a button, and an accelerator key, create a function that enables or disables all of those items, then call that function via after.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685