Issue
I am currently working on a small project to get started with physical computing with Python - using a raspberry pi. I am trying to make a game using 3 LED's and a push button. The 3 LED's will flash in a certain order for different times and you have to click the button when the amber LED is turned on. So far I have tried this code using gpiozero to recognize when the button is turned on and see if it is was within the time frame that the amber led was on.
Game Code: https://codeshare.io/G7qk1j (If link above doesn't work please let me know)
Code:
def gameEasy():
print("Level 1 - trial run")
how_long_to_react = 5
time_till_started = time.time() + 4
while time.time() < time_till_started:
button.when_pressed = None
button.when_pressed = lambda:clickedRight(ambTime, greenTime, time.time())
redLed.on()
time.sleep(2)
redLed.off()
amberLed.on()
ambTime = time.time()
greenTime = ambTime + how_long_to_react
time.sleep(how_long_to_react)
amberLed.off()
greenLed.on()
time.sleep(0.1)
greenLed.off()
time.sleep(10)
At the moment, to try to get it to work, firstly, the red led will turn on for 2 seconds, then the amber led will turn on (currently for 4 seconds just as a test). When the amber LED turns on, that is when the first time is noted to later be checked to see if the button is clicked within the range. My issue is that if the button is clicked before this variable (ambTime) is calculated, then line 58 will trigger an error:
button.when_pressed = lambda:clickedRight(ambTime, greenTime, time.time())
NameError: free variable 'ambTime' referenced before assignment in enclosing scope
I understand why it's happening, but i'm not sure how to 'block' any button clicks before the amber LED turns on. As you can see, I have a while loop to try to block any commands for the first 4 seconds but it acts as a delay rather than a background loop (so it won't start the next code until after the 4 seconds).
I've noticed with this method - button.when_pressed = lambda:clickedRight(ambTime, greenTime, time.time())
- it is is always checking for the button press, no matter where this line is in the code, so it doesn't matter if I put this line just before amberLed.on()
I hope I have explained this right, this has been troubling me for quite a while so any help would much appreciated. I have tried multiprocessing but got really confused with how to integrate it here, but I am open to any suggestions. I completely understand that you may not be able to test your code so I will be happy to try anything and respond with any issues (if there are any) that come up.
Thank you.
Solution
As you have noted; running...
button.when_pressed = lambda:clickedRight(ambTime, greenTime, time.time())
...immediately starts a thread that monitors for button clicks, and if
your lambda
function runs before ambTime
is defined, it will fail.
The easiest solution is probably to initialize ambTime
to some value
before setting up the button.when_pressed
action, e.g:
def gameEasy():
...
ambTime = None
button.when_pressed = lambda:clickedRight(ambTime, greenTime, time.time())
...
And in your clickedRight
function, explicitly check for to see if
ambTime
has a valid value. Maybe something like:
def clickedRight(led_clicked_time, led_after_turnedon_time, button_time):
if led_clicked_time is not None and (
button_time >= led_clicked_time and
button_time <= led_after_turnedon_time
):
print("Yay")
else:
print("Missed")
button.when_pressed = None
Answered By - larsks