Button (GPIO) Pressing Logic in MicroPython - python

I had another question open about iterative menu logic, and the problem morphed into button logic, so I'm separating them, since the original question was truly settled.
My code is as follows:
""" fit: a productivity logger """
import time
import sys
import os
import uhashlib
import machine
import framebuf
from ssd1306 import SSD1306_I2C
def final_print(sec,final_hash,final_survey):
""" leaves the summary on the screen before shutting down """
mins = sec // 60
sec = sec % 60
hours = mins // 60
mins = mins % 60
short_sec = int(sec)
duration = (str(hours) + "/" + str(mins) + "/" + str(short_sec))
oled_show("> fit the"+str(final_hash),str(final_survey),"//"+str(duration))
time.sleep(30)
oled_blank()
def timer_down(f_seconds,timer_focus):
""" counts down for defined period """
now = time.time()
end = now + f_seconds
while now < end:
now = time.time()
fit_progress(now,end,timer_focus,f_seconds)
time.sleep(0.01)
# if button1.value() == 0:
# oled_show("","Ended Manually!","")
# time.sleep(2)
# break
def timer_up(timer_focus):
""" counts up for indefinite period """
now = time.time()
while True:
minutes = int((time.time() - now) / 60)
oled_show(str(timer_focus)," for ",str(minutes))
time.sleep(0.01)
# if button1.value() == 0:
# oled_show("","Ended Manually!","")
# time.sleep(2)
# break
def fit_progress(now,end,timer_focus,f_seconds):
""" tracks progress of a count-down fit and prints to screen """
remain = end - now
f_minutes = int((remain)/60)
j = 1 - (remain / f_seconds)
pct = int(100*j)
oled_show(str(timer_focus),str(f_minutes)+" min",str(pct)+"%")
def debounce(btn):
""" some debounce control """
count = 2
while count > 0:
if btn.value():
count = 2
else:
count -= 1
time.sleep(0.01)
def multi_choice(options):
""" provides multi-choice menus for two-button navigation """
for i in options:
oled_show("> fit",i,"1:yes 2:next")
# Wait for any button press.
while 1:
b1pressed = button1.value()
b2pressed = button2.value()
if b1pressed or b2pressed:
break
if b1pressed:
print( i, "chosen" )
debounce(button1)
return i
# We know B2 was pressed.
debounce(button2)
def oled_show(message1,message2,message3):
""" displays a three line message """
oled.fill(0) # clear the display
oled.text(message1,5,5)
oled.text(message2,5,15)
oled.text(message3,5,25)
oled.show()
def oled_blank():
""" blanks the oled display to avoid burn in """
oled.fill(0)
oled.show()
sda = machine.Pin(4)
scl = machine.Pin(5)
i2c = machine.I2C(0,sda=sda, scl=scl, freq=400000)
oled = SSD1306_I2C(128, 32, i2c)
button1 = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
button2 = machine.Pin(3, machine.Pin.IN, machine.Pin.PULL_UP)
F_TYPE = multi_choice(['30-minute fit','60-minute fit','indefinite fit'])
F_FOCUS = multi_choice(['personal fit','work fit','learn fit','admin fit'])
fStart = time.time()
if F_TYPE == "30-minute fit":
timer_down(1800,F_FOCUS)
elif F_TYPE == "60-minute fit":
timer_down(3600,F_FOCUS)
elif F_TYPE == "indefinite fit":
timer_up(F_FOCUS)
else:
sys.exit()
fEnd = time.time()
F_SURVEY = multi_choice(['went well','went ok','went poorly'])
fDuration = fEnd - fStart
F_HASH = uhashlib.sha256(str(fEnd).encode('utf-8')).digest()
F_HASH_SHORT = F_HASH[0:3]
fitdb = open("data.csv","a")
fitdb.write(str(F_HASH)+","+str(F_TYPE)+","+str(F_FOCUS)+","+str(F_SURVEY)+","+str(fStart)+","+str(fEnd)+","+str(fDuration)+"\n")
fitdb.close()
final_print(fDuration,F_HASH_SHORT,F_SURVEY)
In particular, you can see that I have two buttons defined:
button1 = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
button2 = machine.Pin(3, machine.Pin.IN, machine.Pin.PULL_UP)
And they are primarily used to select from menus with multiple choices:
def debounce(btn):
""" some debounce control """
count = 2
while count > 0:
if btn.value():
count = 2
else:
count -= 1
time.sleep(0.01)
def multi_choice(options):
""" provides multi-choice menus for two-button navigation """
for i in options:
oled_show("> fit",i,"1:yes 2:next")
# Wait for any button press.
while 1:
b1pressed = button1.value()
b2pressed = button2.value()
if b1pressed or b2pressed:
break
if b1pressed:
print( i, "chosen" )
debounce(button1)
return i
# We know B2 was pressed.
debounce(button2)
However, I am encountering an issue whereby the buttons can only be pressed alternately. That is, when the multi_choice function begins, I can press button1 to select the first option, or I can press button2 to scroll to the next option, but, if I press button2, for example, it will not register for a second press (to select a second option), I can only then press button1... if I do that, I can only then press button2 next.
I'm certain this is just a logic issue I'm not seeing.
The buttons are ordinary momentary-closed Cherry MX switches on GPIO pins 2 and 3. They definitely work reliably, but something about this logic is wonky.
The following test works just fine, so it's not the buttons...
import machine
import time
button1 = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
button2 = machine.Pin(3, machine.Pin.IN, machine.Pin.PULL_UP)
while True:
b1pressed = button1.value()
b2pressed = button2.value()
time.sleep(0.01)
b1released = button1.value()
b2released = button2.value()
if b1pressed and not b1released:
print('Button1 pressed!')
if b2pressed and not b2released:
print('Button2 pressed!')
if not b2pressed and b2released:
print('Button2 released!')
elif not b1pressed and b1released:
print('Button1 released!')
I added in some print statements to debug this, and I can see the buttons taking and holding values. I feel like I need to tune in an artificial reset, maybe that's something I can do in debounce? I tried a few things, but I'm not making progress so far.
def debounce(btn):
""" some debounce control """
count = 2
while count > 0:
if btn.value():
count = 2
else:
count -= 1
time.sleep(0.01)
def multi_choice(options):
""" provides multi-choice menus for two-button navigation """
for i in options:
print("below for")
print("button 1 below for",button1.value())
print("button 2 below for",button2.value())
oled_show(" fit",i,"1:sel 2:next")
while 1:
print("below while")
print("button 1 below while",button1.value())
print("button 2 below while",button2.value())
b1pressed = button1.value()
b2pressed = button2.value()
if b1pressed or b2pressed:
print("below first if")
print("button 1 first if",button1.value())
print("button 2 first if",button2.value())
break
if b1pressed:
print("below second if")
print("button 1 second if",button1.value())
print("button 2 second if",button2.value())
debounce(button1)
return i
debounce(button2)
and the output of the above debug prints:
>>> %Run -c $EDITOR_CONTENT
below for
button 1 below for 0
button 2 below for 1
below while
button 1 below while 0
button 2 below while 1
below first if
button 1 first if 0
button 2 first if 1
below for
button 1 below for 1
button 2 below for 0
below while
button 1 below while 1
button 2 below while 0
below first if
button 1 first if 1
button 2 first if 0
below second if
button 1 second if 1
button 2 second if 0
below for
button 1 below for 0
button 2 below for 1
below while
button 1 below while 0
button 2 below while 1
below first if
button 1 first if 0
button 2 first if 1
below for
button 1 below for 1
button 2 below for 0
below while
button 1 below while 1
button 2 below while 0
below first if
button 1 first if 1
button 2 first if 0
below second if
button 1 second if 1
button 2 second if 0

With both of your buttons tied to ground, and using PULL_UP the code is just:
import machine
button1 = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
button2 = machine.Pin(3, machine.Pin.IN, machine.Pin.PULL_UP)
while True:
print('Button1 down!') if not button1.value() else print('Button1 up!')
print('Button2 down!') if not button2.value() else print('Button2 up!')
The logic is simple. Your button is tied to ground on one end and pulled up on the other. When you press that button it's going to connect the pin of the MCU to ground. When the button is pressed value() will be zero, and when it is released value() will be one. If you want value() to be positive when you press the button then you need to tie the button to the Vcc rail and use PULL_DOWN on the MCU pin.
All that debouncing code probably isn't even necessary, and even if it was necessary creating a loop to handle it isn't the answer. What if your button doesn't bounce ~ now you're just stuck in a loop. Get a 74HC14 or make a simple RC network if you're worried about bouncing. Assigning all these arbitrary values to button states and sticking buttons in loops that block your other buttons is just a bunch of noise. It's very simple: You have one question that you want to ask twice. "Is this button pressed" ~ so just "ask" that question twice and move on. It doesn't take 20 lines of code to determine if something is 1 or 0.
Aside:
You do not have a separate "GND". All your "GND" pins are connected to the same "GND". You have one power source right? Well, one is one. Cutting a cake into 8 pieces doesn't give you 8 cakes.

Ok, with the help from everybody here, I figured this out. Just needed to add a little sleep to avoid spill-over of the keypress from the previous while loop.
def multi_choice(options):
""" provides multi-choice menus for two-button navigation """
for i in options:
oled_show(" fit",i,"1:sel 2:next")
time.sleep(0.5)
while 1:
if not button1.value():
return i
if not button2.value():
break
Thanks all!

Related

How can I use a single button to pause and unpause timer in tkinter? (without using pygame)

I'm working on a simple timer that counts down 30 mins for me to study and 5 mins for a break. So far, the start_timer function and count_down function work well, I just cannot figure out how to write the pause function. I did some research for a few days. Most articles are using pygame or to bind with different keys. I am wondering what function I should use for one tkinter button to pause/unpause my timer if something comes up and I want to pause the timer till I'm back.
Thank you #TimRoberts, I can pause the timer now. However, I don't know how to unpause the timer to let it continue counting down.
from tkinter import *
import math
WORK_MIN = 30
BREAK_MIN = 5
reps = 0
paused = False
# --------------------------- TIMER ---------------------------- #
def start_timer():
global reps
reps += 1
work_sec = WORK_MIN * 60
break_sec = BREAK_MIN * 60
if reps % 2 == 1:
title_label.config(text="Study")
count_down(work_sec)
else:
title_label.config(text="Break")
count_down(break_sec)
window.attributes('-topmost', 0)
# ------------------------ COUNTDOWN--------------------------- #
def count_down(count):
global paused
count_min = math.floor(count / 60)
count_sec = count % 60
if count_min < 10:
count_min = f"0{count_min}"
if count_sec < 10:
count_sec = f"0{count_sec}"
canvas.itemconfig(timer_text, text=f"{count_min}:{count_sec}" )
if count > 0:
if not paused:
count -= 1
window.after(1000, count_down, count-1)
else:
start_timer()
# ---------------------------- PAUSE ------------------------------- #
def pause_function():
global paused
paused = not paused
# ---------------------------- UI ------------------------------- #
window = Tk()
title_label = Label(text="Timer")
title_label.grid(column=1, row=0)
check_marks = Label(text="")
check_marks.grid(column=1, row=4)
canvas = Canvas(width=200, height=224, bg="lightblue")
timer_text = canvas.create_text(100, 128, text="00:00", fill="white", font=("Courier", 45, "bold"))
canvas.grid(column=1, row=1)
start_button = Button(text="Start", command=start_timer)
start_button.grid(column=0, row=2)
pause_button = Button(text="Pause", command=pause_function)
pause_button.grid(column=2, row=2)
window.mainloop()
You need to do the "after" call even if you're paused, otherwise you'll never notice when you unpause. Also, since you're decrementing count once, you don't need to do it again:
def count_down(count):
count_min = count // 60
count_sec = count % 60
canvas.itemconfig(timer_text, text=f"{count_min:02d}:{count_sec:02d}" )
if count:
if not paused:
count -= 1
window.after(1000, count_down, count)
else:
start_timer()
If you want to be tricky, you could use:
if count:
count -= not paused
since True is 1 and False is 0.

Two-Button Menu Iteration

I've got a script that I'm adapting to micropython on a 2040, and I want to use two buttons to navigate the menu structure. I can't figure out how to make the iterate loop in the multi-choice menus work right... here's what I've got so far:
""" fit: a productivity logger """
import time
import sys
import os
import uhashlib
import machine
def final_print(sec,final_hash,final_survey):
""" leaves the summary on the screen before shutting down """
mins = sec // 60
sec = sec % 60
hours = mins // 60
mins = mins % 60
short_sec = int(sec)
duration = (str(hours) + "/" + str(mins) + "/" + str(short_sec))
print("> fit the",str(final_hash)," went ",str(final_survey)," lasted //",str(duration))
def timer_down(f_seconds,timer_focus):
""" counts down for defined period """
now = time.time()
end = now + f_seconds
while now < end:
now = time.time()
fit_progress(now,end,timer_focus,f_seconds)
b1pressed = button1.value()
time.sleep(0.01)
if not b1pressed:
print('Ended Manually!')
break
def timer_up(timer_focus):
""" counts up for indefinite period """
now = time.time()
while True:
minutes = int((time.time() - now) / 60)
print(str(timer_focus)," for ",str(minutes))
b1pressed = button1.value()
time.sleep(0.01)
if not b1pressed:
print('Ended Manually!')
break
def fit_progress(now,end,timer_focus,f_seconds):
""" tracks progress of a count-down fit and prints to screen """
remain = end - now
f_minutes = int((remain)/60)
j = 1 - (remain / f_seconds)
pct = int(100*j)
print(str(timer_focus),str(f_minutes),str(pct))
def multi_choice(options):
done = 0
while done == 0:
for i in options:
b1pressed = button1.value()
b2pressed = button2.value()
time.sleep(.01)
b1released = button1.value()
b2released = button2.value()
if b2pressed and not b1pressed:
print(i," b2 pressed")
continue
if b1pressed and not b2pressed:
print(i," b1 pressed")
time.sleep(2)
return i
button1 = machine.Pin(2, machine.Pin.IN, machine.Pin.PULL_UP)
button2 = machine.Pin(3, machine.Pin.IN, machine.Pin.PULL_UP)
print("format?")
fType = multi_choice(['30-minute fit','60-minute fit','indefinite fit'])
print(fType," selected")
print("focus?")
F_FOCUS = multi_choice(['personal fit','work fit','learn fit','admin fit'])
print(F_FOCUS," selected")
fStart = time.time()
if fType == "30-minute fit":
timer_down(1800,F_FOCUS)
elif fType == "60-minute fit":
timer_down(3600,F_FOCUS)
elif fType == "indefinite fit":
timer_up(F_FOCUS)
else:
sys.exit()
fEnd = time.time()
print("sentiment?")
F_SURVEY = multi_choice(['+','=','-'])
print(F_SURVEY," selected")
fDuration = fEnd - fStart
F_HASH = uhashlib.sha256(str(fEnd).encode('utf-8')).digest()
F_HASH_SHORT = F_HASH[0:3]
fitdb = open("data.csv","a")
fitdb.write(str(F_HASH)+","+str(fType)+","+str(F_FOCUS)+","+str(F_SURVEY)+","+str(fStart)+","+str(fEnd)+","+str(fDuration)+"\n")
fitdb.close()
final_print(fDuration,F_HASH_SHORT,F_SURVEY)
print(F_HASH_SHORT," ",F_HASH)
In particular, this is the logic I'm wrangling with:
def multi_choice(options):
done = 0
while done == 0:
for i in options:
b1pressed = button1.value()
b2pressed = button2.value()
time.sleep(.01)
b1released = button1.value()
b2released = button2.value()
if b2pressed and not b1pressed:
print(i," b2 pressed")
continue
if b1pressed and not b2pressed:
print(i," b1 pressed")
time.sleep(2)
return i
Here's what I'm wanting to do:
For each item in the set,
Display the item, wait for button press
If button 1 is pressed, select that item, return the item.
If button 2 is pressed, display the next item, wait for button press.
(iterate until button 1 pressed to select item)
Again, this is micropython, so I don't have all the modules you'd think available... woudl be best to do this in raw code.
0.01 second is way too short. The key is, after you detect "button down", you need to wait for "button up". You need something like:
def wait_for_btn_up(btn):
count = 2
while count > 0:
if btn.value();
count = 2
else:
count -= 1
time.sleep(0.01)
def multi_choice(options):
for i in options:
print( "trying", i )
# Wait for any button press.
while 1:
b1pressed = button1.value()
b2pressed = button2.value()
if b1pressed or b2pressed:
break
if b1pressed:
print( i, "chosen" )
wait_for_btn_up(button1)
return i
# We know B2 was pressed.
wait_for_btn_up(button2)

time function always gives me 0.0 output

Im trying to make a CPS counter, and when I reach 100 clicks, its supposed to print "test" and also print the time it took to get to 100 clicks. But it always gives 0.0 as the time output.
import tkinter
import time
counter = tkinter.Tk()
clicks = 0
def addClick():
global clicks
clicks = clicks + 1
lbl.configure(text=clicks)
start = time.time()
if clicks == 100:
print("test")
end = time.time()
print(start - end)
lbl = tkinter.Label(counter, text = clicks)
lbl.pack()
btn = tkinter.Button(counter, text="Click here", command=addClick)
btn.pack()
counter.mainloop()
...
start = time.time()
if clicks == 100:
print("test")
end = time.time()
print(start - end)
You keep restarting start after every click. A possible solution would be to start it only after the first click. This will require start to be a global variable as well.
Also note that you should do end - start, not start - end.
clicks = 0
start = None
...
global clicks
global start
...
if clicks == 1:
# instantiating 'start' only if it was the first click
start = time.time()
elif clicks == 100:
print("test")
end = time.time()
print(end - start)
However, using global variables is quite a code-smell and an anti-pattern, and we already have 2 of them in such a tiny program.
You can try to wrap them in a data-structure such as a dict:
import tkinter
import time
counter = tkinter.Tk()
data = {'clicks': 0, 'start': None}
def addClick():
data['clicks'] += 1
lbl.configure(text=data['clicks'])
if data['clicks'] == 1:
# instantiating 'start' only if it was the first click
data['start'] = time.time()
elif data['clicks'] == 100:
print("test")
end = time.time()
print(end - data['start'])
lbl = tkinter.Label(counter, text=data['clicks'])
lbl.pack()
btn = tkinter.Button(counter, text="Click here", command=addClick)
btn.pack()
counter.mainloop()
Another, real-world fitting solution would be to wrap the entire tkinter app in a class, that can keep track of its own state.

Python Clicker Game

I have come up with some code that I am using for a clicker game, kinda like Cookie Clicker.
from tkinter import *
import time
master = Tk()
def uiPrint():
info()
print("")
print(click)
blankLine()
def info():
print("Double click purchases need 50 clicks!")
print("Auto clicker purchases need 75 clicks!")
info()
click = 0
mult = 1
dcp1 = 0
def blankLine():
for i in range(20):
print("")
def purchaseDoubleClicksCommand():
global click
global mult
if click < 5:
print("Not enough clicks!")
blankLine()
elif click >= 5:
mult = mult*2
click = click - 5
print("Double Clicks Purchased!")
blankLine()
def purchaseAutoClickerCommand():
global click
if click < 7:
print("Not enough clicks!")
blankLine()
elif click >= 7:
click = click - 7
print("Auto clicker purchased!")
while True:
click = click + 1
time.sleep(1)
def buttonCommand():
global click
global mult
click += 1*(mult)
uiPrint()
if click == 100:
print('''Achievement Unlocked: Junior Clicker!
BONUS 100 clicks!''')
click += 100
elif click == 400:
print ('''Achievement Unlocked: Little Ninja Clicks!
BONUS 200!''')
click += 300
elif click == 1500:
print ('''Achievement Unlocked: Click Ninja Master!
QUAD CLICKS!''')
mult = mult * 4
elif click == 3000:
print ('''Achievement Unlocked: Jackie Chan Style!
8 TIMES THE CLICKS!''')
mult = mult * 8
mainClickButton = Button(master, text="Click!", command = buttonCommand)
mainClickButton.pack()
purchaseDoubleClickButton = Button(master, text="Purchase Double Clicks", command = purchaseDoubleClicksCommand)
purchaseDoubleClickButton.pack()
purchaseAutoClickerButton = Button(master, text="Purchase Auto Clicker", command = purchaseAutoClickerCommand)
purchaseAutoClickerButton.pack()
master.title("Clicker! v0.0.6")
master.geometry("%sx%s+%s+%s" % (200,70,512,512))
mainloop()
This is the code that I have so far. I am trying to add an Auto Clicker that you can buy through a button. I have found one other post that is about this, but the solution for that uses PyMouse which 1.) I can't get installed (Trust me I've tried everything) and 2.) Don't want to imitate user input.
So this is the code in question.
def purchaseAutoClickerCommand():
global click
if click < 7:
print("Not enough clicks!")
blankLine()
elif click >= 7:
click = click - 7
print("Auto clicker purchased!")
while True:
click = click + 1
time.sleep(1)
And the code in charge of the button
purchaseAutoClickerButton = Button(master, text="Purchase Auto Clicker", command = purchaseAutoClickerCommand)
purchaseAutoClickerButton.pack()
When I hit the button that says "Purchase Auto Clicker", not only do I not get the number of clicks to increase by one per second, but the entire app crashes.
So my main question is, how can I have the number of clicks increase by one per second by the game itself (without the user needing to keep the mouse on the "Click!" button), and how can I have the Auto Clickers stack when more than one are bought (plus I would like to not have the program crash as soon as I try to buy it).
Editing this for Scratso to see:
This is what I have changed to the code
click = 0
mult = 1
dcp1 = 0
autoclickers = 0
def blankLine():
for i in range(20):
print("")
def purchaseDoubleClicksCommand():
global click
global mult
if click < 5:
print("Not enough clicks!")
blankLine()
elif click >= 5:
mult = mult*2
click = click - 5
print("Double Clicks Purchased!")
blankLine()
def purchaseAutoClickerCommand():
global click
global autoclickers
if click < 7:
print("Not enough clicks!")
blankLine()
elif click >= 7:
autoclickers += 1 # add an autoclicker
click = click - 7
print("Auto clicker purchased!")
And at the end of the code I have added
while True:
mainClickButton = Button(master, text="Click!", command = buttonCommand)
mainClickButton.pack()
purchaseDoubleClickButton = Button(master, text="Purchase Double Clicks", command = purchaseDoubleClicksCommand)
purchaseDoubleClickButton.pack()
purchaseAutoClickerButton = Button(master, text="Purchase Auto Clicker", command = purchaseAutoClickerCommand)
purchaseAutoClickerButton.pack()
master.title("Clicker! v0.0.6")
master.geometry("%sx%s+%s+%s" % (200,70,512,512))
mainloop()
for autoclicker in range(autoclickers):
click += 1
time.sleep(1)
The problem with using sleep() in a tkinter application is that it messes with the way the GUI is updated. Instead, call after() on the root tkinter object to tell it to execute the given command (function) after the given number of milliseconds have elapsed. This after() call will be placed within the function itself, so that, after calling this function normally, it will be called again one second later.
autoclickers=0 # start autoclickers at 0
def purchaseAutoClickerCommand():
global click
global autoclickers # declare global
if click < 7:
print("Not enough clicks!")
blankLine()
else:
click -= 7 # pay for an autoclicker
print("Auto clicker purchased!")
autoclickers += 1 # receive an autoclicker
def autoclick():
global master
global click
global autoclickers
click += autoclickers # get clicks from autoclickers
master.after(1000, autoclick) # do this again 1 second later
autoclick() # start benefiting from all existing autoclickers
well, I noticed that using time will just lag everything. Try using timers or make it add .00001 clicks so it will make it kinda like a timer.
What I would recommend for just the clicking is just using a module like pyautogui or something which can type keys and click on its own, unless you were asking for like a autoclicker but you can click on the side
EDIT
you could use a while loop which adds 1 per second
I downloaded an autoclicker my self and after 5 seconds of holding my mouse over the click button and ten over the double button i achieved this amount of clicks. (copy, paste)
755556783768937868934768457868378674578897653689974322127907543098763683978387683767937784870388737657475555678376893786893476845786837867457889765368997432212790754309876368397838768376793778487038873765747555567837689378689347684578683786745788976536899743221279075430987636839783876837679377848703887376574755556783768937868934768457868378674578897653689974322127907543098763688934768457868378674578897653689974322127907543098763683978387683767937784870388737657475555678376893786893476445533384578683786745788976536899743221279075430987636839783876837679377848703887376574755556783768937868934224457684578683786745788976536899743221279075430987636839783876837679377848703887376574755556783768937868934768457868378674578897653689974322127907543098763683978387683767937784870388737657475555678376893786893476845786837867457889765368997432212790754309876366666666664344667645544448397838768376793778487038873765747555567837689378689347684578683786745788976536899743221279075430987636839783876837679377848703887333765747555567837689378689347684578683786745788976536899743221279075430987636839783876837679377848703887376574755556783768937868934768457868378674578897653689974322127907543098783978383333368376793778487038873765747555567837689378689347684578683786745788976536899743221279075430987636839783876837679377848703887376574755556783768937868934768457868378674578897653689974322127907543098763683978387683767937784870388737657475555678376893786893476845786837867457889765666536899743221279075430987636839783876837679377848703887376574755556783768937868934768457868378674578897653689974322127907543098763683978387683767937784870388737657475555678376893786893476845786837867457889765368997432212790754309876368397838768376793778487038873765747555567837689378689347684578683786745788976536899743221279075430987636839783876837679377848703887376574755556783768937868934768457868378674578897653689974322127907543098763683978387683767937784870388737657475555678376893786893476845786837867457889765368997432212790754309876368397838768376793778487038873765747555567837689378689347684578683786745788976536899743221279075430987636839783876837679377848703887376574

"after" looping indefinitely: never entering mainloop

This is my first post. I started coding when considering a career swap two months ago and am working on a Tetris clone. I've implemented most of the core features, but cannot get the game to refresh continually with an after loop.
I'm using Tkinter to produce my Gui and am trying out event oriented programming.
My understanding is that after(Time, Event) from Tkinter should schedule whatever the Event callback function is to occur after a delay specified by Time. I think that the code is supposed to continue executing subsequent items after this.
My frame refresh function (game.updateBoard()) does most of the necessary events for tetris to work, then calls itself using after. I call it once when initializing an instance of the game.
Instead of proceeding to mainloop(), the game.updateboard() function calls itself via after indefinitely.
I suspect that it is not behaving how I thought after worked which would be to continue to execute the script until the specified delay occurs. I think it is waiting for the callback to terminate to continue.
I tried to find a resource on this but could not.
If you have suggestions for fixing this question, the attached code, or for coding in general, I am very happy to hear them! This is a learning process and I'll gladly try pretty much anything you suggest.
Here is the relevant portion of the code:
class game():
def __init__(self): #Set up board and image board
self.pieces = ["L","J","S","Z","T","O","I"]
self.board = boardFrame()
self.root = Tk()
self.root.title("Tetris")
self.root.geometry("250x525")
self.frame = Frame(self.root)
#set up black and green squares for display
self.bSquare = "bsquare.gif"
self.gSquare = "square.gif"
self.rSquare = "rsquare.gif"
self.image0 = PhotoImage(file = self.bSquare)
self.image1 = PhotoImage(file = self.gSquare)
self.image2 = PhotoImage(file = self.rSquare)
#get an initial piece to work with
self.activeBlock = piece(self.pieces[random.randint(0,6)])
#Tells program to lower block every half second
self.blockTimer = 0
self.updateBoard()
self.root.bind('<KeyPress-Up>', self.turn)
self.root.bind('<KeyPress-Right>', self.moveR)
self.root.bind('<KeyPress-Left>', self.moveL)
self.root.bind('<KeyPress-Down>',self.moveD)
print("Entering mainloop")
self.root.mainloop()
def turn(self, event):
self.activeBlock.deOccupy(self.board)
self.activeBlock.turn()
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def moveR(self, event):
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([1,0], self.board)
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def moveL(self, event):
if self.activeBlock.checkLeft(self.board) == False:
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([-1,0], self.board)
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def moveD(self, event): #find
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([0,-1],self.board)
if self.activeBlock.checkBottom(self.board) == True:
self.activeBlock.occupy(self.board)
self.activeBlock = piece(self.pieces[random.randint(0,6)])
## self.activeBlock = piece(self.pieces[1])
print("bottomed")
self.activeBlock.occupy(self.board)
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
def drawGrid(self, dGrid):
#Generate squares to match tetris board
for widget in self.frame.children.values():
widget.destroy()
self.activeBlock.occupy(self.board)
for x in range(9,-1,-1):
for y in range(20,-1,-1):
if self.board.grid[x][y] == 1:
self.frame.displayA = Label(self.frame, image=self.image1)
## self.frame.displayA.image = self.image1
self.frame.displayA.grid(row=21-y, column=x)
else:
self.frame.displayA = Label(self.frame, image = self.image0)
## self.frame.displayA.image = self.image0
self.frame.displayA.grid(row=21-y, column=x)
self.frame.displayA = Label(self.frame, image = self.image2)
self.frame.displayA.grid(row = 21 - self.activeBlock.center[1], column = self.activeBlock.center[0])
self.frame.grid()
def updateBoard(self):
self.blockTimer += 1
"print updateBoard Loop"
## 1)check for keyboard commands
#1.1 move block by keyboard commands
#2) see if block has bottomed out, if it has, have it enter itself into the grid and generate a new block.
if self.activeBlock.checkBottom(self.board) == True:
self.activeBlock.occupy(self.board)
self.activeBlock = piece(self.pieces[random.randint(0,6)])
print("bottomed")
self.activeBlock.occupy(self.board)
#2.2 - if block has not bottomed and 50 frames (~.5 seconds) have passed, move the active block down a square after clearing its old space.
elif self.blockTimer%12 == 0:
self.activeBlock.deOccupy(self.board)
self.activeBlock.updatePos([0,-1], self.board)
self.activeBlock.occupy(self.board)
## 4) check for filled rows
for y in range(1,21):
for x in range(10):
rowFull = True
if self.board.grid[x][y] == 0:
rowFull == False
#4.1 if any row is filled, delete it and then move all rows above the deleted row down by one
if rowFull == True:
for x2 in range(10):
self.board.grid[x2][y] = 0
for y2 in range(y+1,21):
if self.board.grid[x2][y2] == 1:
self.board.grid[x2][y2] = 0
self.board.grid[x2][y2-1] = 1
#4.11 if the row is full and the row above it was full, delete the row again as well as the row above it, and move all rows down by 2
for x in range(10):
rowFull = True
if self.board.grid[x][y] == 0:
rowFull == False
if rowFull == True:
for x2 in range(10):
try:
self.board.grid[x2][y] = 0
self.board.grid[x2][y+1] = 0
except:
pass
for y2 in range(y+2,21):
try:
if self.board.grid[x2][y2] == 1:
self.board.grid[x2][y2] = 0
self.board.grid[x2][y2-2] = 1
except:
pass
#5) if there is a block in the top row, end the game loop
for x in range(10):
if self.board.grid[x][20] == 1:
game = "over"
#6) update image
self.activeBlock.occupy(self.board)
self.drawGrid(self.board.grid)
self.frame.after(500, self.updateBoard())
Game = game()
You want to do self.frame.after(500, self.updateBoard).
The difference here is subtle, (self.updateBoard instead of self.updateBoard()). In your version, you're passing the result of your function to the after method instead of passing the function. This results in the infinite recursion that you described.

Categories