How to use function in tkinter mainloop() [duplicate] - python

This question already has answers here:
Tkinter — executing functions over time
(2 answers)
Closed 4 years ago.
I decided to try out Python and it's been fun so far. However while messing around with tkinter I encountered a problem which I haven't been able to solve for hours. I've read some things and tried different stuff but nothing works.
I've got the code so far that I think the program should run fine. Except for the fact that I can't make it loop and thus update automatically.
So my question is: how can I call a function with tkinters loop options in an infite loop fashion?
Simple game of life:
I wrote basicly 2 classes. A Matrix which stores and handles the single cells
and the game itself which utilizes the matrix class through some game logic and basic user input.
First the game class as there is my loop problem:
from tkinter import *
from ButtonMatrix import *
class Conway:
def __init__(self, master, size = 20, cell_size = 2):
self.is_running = True
self.matrix = ButtonMatrix(master,size,cell_size)
self.matrix.randomize()
self.matrix.count_neighbours()
self.master = master
# playbutton sets boolean for running the program in a loop
self.playbutton = Button(master, text = str(self.is_running), command = self.stop)
self.playbutton.grid(row = 0 , column = size +1 )
#Test button to trigger the next generation manually. Works as itended.
self.next = Button(master, text="next", command = self.play)
self.next.grid(row = 1, column = size +1)
def play(self): # Calculates and sets the next generation. Intended to be used in a loop
if self.is_running:
self.apply_ruleset()
self.matrix.count_neighbours()
self.apply_colors()
def apply_ruleset(self):
#The ruleset of conways game of life. I wish i knew how to adress each element
#without using these two ugly loops all the time
size = len(self.matrix.cells)
for x in range (size):
for y in range (size):
if self.cell(x,y).is_alive():
if self.cell(x,y).neighbours < 2 or self.cell(x,y).neighbours > 3:
self.cell(x,y).toggle()
if not self.cell(x,y).is_alive() and self.cell(x,y).neighbours == 3:
self.cell(x,y).toggle()
def apply_colors(self): #Some flashy colors just for fun
size = len(self.matrix.cells)
for x in range (size):
for y in range (size):
if self.cell(x,y).is_alive():
if self.cell(x,y).neighbours < 2 or self.cell(x,y).neighbours > 3:
self.cell(x,y).button.configure(bg = "chartreuse3")
if not self.cell(x,y).is_alive() and self.cell(x,y).neighbours == 3:
self.cell(x,y).button.configure(bg = "lightgreen")
def cell(self,x,y):
return self.matrix.cell(x,y)
def start (self): #start and stop set the boolean for the loop. They work and switch the state properly
self.is_running = True
self.playbutton.configure(text=str(self.is_running), command =self.stop)
def stop (self):
self.is_running = False
self.playbutton.configure(text=str(self.is_running), command =self.start)
#Test program. I can't make the loop work. Manual update via next button works however
root = Tk()
conway = Conway(root)
root.after(1000, conway.play())
root.mainloop()
The Matrix (only for interested readers):
from tkinter import *
from random import randint
class Cell:
def __init__(self,master, cell_size = 1):
self.alive = False
self.neighbours = 0
# initializes a squares shaped button that fills the grid cell
self.frame = Frame(master, width= cell_size*16, height = cell_size*16)
self.button = Button(self.frame, text = self.neighbours, command = self.toggle, bg ="lightgray")
self.frame.grid_propagate(False)
self.frame.columnconfigure(0, weight=1)
self.frame.rowconfigure(0,weight=1)
self.button.grid(sticky="wens")
def is_alive(self):
return self.alive
def add_neighbour(self):
self.neighbours += 1
def toggle (self):
if self.is_alive() :
self.alive = False
self.button.configure( bg = "lightgray")
else:
self.alive = True
self.button.configure( bg = "green2")
class ButtonMatrix:
def __init__(self, master, size = 3, cell_size = 3):
self.master = master
self.size = size
self.cell_size = cell_size
self.cells = []
for x in range (self.size):
row = []
self.cells.append(row)
self.set_cells()
def cell(self, x, y):
return self.cells[x][y]
def set_cells(self):
for x in range (self.size):
for y in range (self.size):
self.cells[x] += [Cell(self.master, self.cell_size)]
self.cell(x,y).frame.grid(row=x,column=y)
def count_neighbours(self): # Checks 8 sourounding neighbours for their stats and sets a neighbour counter
for x in range(self.size):
for y in range(self.size):
self.cell(x,y).neighbours = 0
if y < self.size-1:
if self.cell(x,y+1).is_alive(): self.cell(x,y).add_neighbour() # Right
if x > 0 and self.cell(x-1,y+1).is_alive(): self.cell(x,y).add_neighbour() #Top Right
if x < self.size-1 and self.cell(x+1,y+1).is_alive(): self.cell(x,y).add_neighbour() #Bottom Right
if x > 0 and self.cell(x-1,y).is_alive(): self.cell(x,y).add_neighbour()# Top
if x < self.size-1 and self.cell(x+1,y).is_alive():self.cell(x,y).add_neighbour() #Bottom
if y > 0:
if self.cell(x,y-1).is_alive(): self.cell(x,y).add_neighbour() # Left
if x > 0 and self.cell(x-1,y-1).is_alive(): self.cell(x,y).add_neighbour() #Top Left
if x < self.size-1 and self.cell(x+1,y-1).is_alive(): self.cell(x,y).add_neighbour() #Bottom Left
self.cell(x,y).button.configure(text = self.cell(x,y).neighbours)
def randomize (self):
for x in range(self.size):
for y in range(self.size):
if self.cell(x,y).is_alive(): self.cell(x,y).toggle()
rando = randint(0,2)
if rando == 1: self.cell(x,y).toggle()

There are two problems with your code:
root.after(1000, conway.play())
First, you're not telling Tkinter to call conway.play after 1 second, you're calling conway.play() right now, which returns None, and then telling Tkinter to call None after 1 second. You want to pass the function, not call it:
root.after(1000, conway.play)
Meanwhile, after does not mean "call this function every 1000ms", it means "call this function once, after 1000ms, and then never again". The easy way around this is to just have the function ask to be called again in another 1000ms:
def play(self): # Calculates and sets the next generation. Itended to use in a loop
if self.is_running:
self.apply_ruleset()
self.matrix.count_neighbours()
self.apply_colors()
self.master.after(1000, self.play)
This is explained in the docs for after:
This method registers a callback function that will be called after a given number of milliseconds. Tkinter only guarantees that the callback will not be called earlier than that; if the system is busy, the actual delay may be much longer.
The callback is only called once for each call to this method. To keep calling the callback, you need to reregister the callback inside itself:
class App:
def __init__(self, master):
self.master = master
self.poll() # start polling
def poll(self):
... do something ...
self.master.after(100, self.poll)
(I'm assuming nobody's going to care if your timing drifts a little bit, so after an hour you might have 3549 steps or 3627 instead of 3600+/-1. If that's a problem. you have to get a bit more complicated.)

Related

Is it possible to suspend rendering in tkinter?

I have a GUI with a large number of widgets and each widget has a number of settings. Plus, I have to make a number of calls to get all of the settings. So, it takes a couple of seconds to set the GUI up. As it is now, the GUI starts rendering while I am still setting it up. It looks horrible and it slows down the completion of the final form.
Is there a way I can tell tkinter to stop rendering until the GUI is completely set up and then resume rendering?
I know it's possible to do this in WinForms. In that case, I can just call SuspendLayout() to stop it from rendering while I'm changing it. Does tkinter have a similar mechanism?
Edit ---
#martineau - Here is some test code to give you a better idea of what I am trying to do.
from array import *
from threading import Thread
import tkinter as tk
from tkinter import *
import time
arr=[]
isInit = False
def BuildGUI(value):
"""Draw out inital widgets """
global isInit
rows, cols = (8, 8)
root = Tk()
for i in range(rows):
col = []
for j in range(cols):
led = Label(root, text="", bg="#000000", height = 2, width = 3)
led.grid(row=j, column=i)
col.append(led)
arr.append(col)
print(arr)
isInit = True
#wait for events
root.mainloop()
class AnimTest:
""" Animation Library"""
def begin(self):
if isInit == False:
x = Thread(target=BuildGUI, args=(1,))
x.daemon = True
x.start()
while isInit == False:
print("initializing...")
def clear(self):
for x in range(0,8):
for y in range(0,8):
self.set_pixel(x, y, 0)
#this happens at the start of drawing a new frame
#i want to suspend rendering here
def set_pixel(self, dispValY, dispValX, mode):
pxl = arr[dispValX][dispValY]
if mode == 0: pxl.config(bg="#000000")
else: pxl.config(bg="#00FF00")
#this is used to draw out the fram
def write_display(self):
pass
#i want everything to be rendered when
#this is called.
def runTest():
anim = AnimTest()
anim.begin()
x = 0
y = 2
dx = dy = 1
while True:
x = x + dx
if (x <= 0 or x >= 7): dx = -dx
y = y + dy
if (y <= 0 or y >= 7):dy = -dy
anim.clear()
anim.set_pixel(x,y,1)
anim.write_display()
time.sleep(1/60)
if __name__ == '__main__':
runTest()
This test code should display an animation of a bouncing dot. This particular animation doesn't look that bad. But when I try to make more complex images, it looks very flashy.
I want it to suspend rendering when clear() is called and resume rendering when write_display() is called.
Tkinter can't render anything until either update has been called, or mainloop() is running and allowed to continue. In other words, the default is to not render anything until the code which creates the UI has finished defining the UI.
If you find yourself in the situation where update is being called while you are in the process of defining the UI but you don't want the screen to be updated, the best solution is to either withdraw the window until it is ready, or add one frame between the window and all of the other widgets and then leave adding the frame to the window as your final step.

How does the after method works in tkinter?

I am new at programing and I am trying to make a simple animation to better learn. I have just learned python (still nooby) and starting to learn tkinter.
I am trying to make an animation of the Conway's Game of Life because it has very simple principles and looks cool.
I have manage to actually make my code work but I really dont understand how.
The thing is that the method after I cannot understand how it works.
The part of the code that I dont understand is the method called start.
I really dont understand how "loop finished" can be printed before startloop function returns None (which it should be the same as saying the animation hasnt stop yet)
import tkinter as tk
width = 1400
height = 600
dist = 5
drawlines = False
celstate = set()
numcol = width//dist
numrow = height//dist
def getdeadcells(setcells):
global celstate
deadcells = set()
for cell in setcells:
i, j = cell
list = [(i-1, j-1), (i, j-1), (i+1, j-1),
(i-1, j), (i+1, j), (i-1, j+1), (i, j+1), (i+1, j+1)]
for cel in list:
if cel not in celstate:
deadcells.add(cel)
return deadcells
def getnewstate():
def neight(cell):
i, j = cell
count = 0
list = [(i-1, j-1), (i, j-1), (i+1, j-1),
(i-1, j), (i+1, j), (i-1, j+1), (i, j+1), (i+1, j+1)]
for cel in list:
if cel in celstate:
count +=1
return count
global celstate, numcol, numrow
alivecells = celstate.copy()
deadcells = getdeadcells(alivecells)
newstate = set()
for cell in alivecells:
neigh = neight(cell)
if neigh == 2 or neigh == 3:
newstate.add(cell)
for cell in deadcells:
neigh = neight(cell)
if neigh == 3:
newstate.add(cell)
if newstate == celstate:
return None
else:
celstate = newstate
if len(newstate) == 0:
return ""
else:
return newstate
def getcords(x, y):
col = x//dist
row = y//dist
return (col, row)
class GUI():
def __init__(self, master, width, height, dist):
master.geometry("{}x{}".format(width, height))
master.bind("<Key>", self.start)
self.master = master
self.width = width
self.height = height
self.dist = dist
self.canvas = tk.Canvas(master, width=width, height=height)
self.canvas.pack(expand=True)
self.drawlimits(dist)
def start(self, event):
if event.keycode == 32 or event.keycode == 13:
def startloop():
newstate = getnewstate()
if newstate == None:
return None
elif newstate == "":
self.canvas.delete("rect")
return None
else:
self.canvas.delete("rect")
self.fillrects(list(newstate))
self.master.after(100, startloop)
startloop()
print("loop finished")
def drawlimits(self, dist):
if self.width % dist == 0 and self.height % dist == 0:
self.canvas.bind("<B1-Motion>", self.drawcells)
self.canvas.bind("<ButtonRelease-1>", self.drawcells)
self.canvas.bind("<B3-Motion>", self.killcell)
self.canvas.bind("<ButtonRelease-3>", self.killcell)
if drawlines:
xsteps = self.width/dist
ysteps = self.height/dist
for num in range(int(xsteps-1)):
self.canvas.create_line((num+1)*dist, 0, (num+1)*dist, self.height)
for num in range(int(ysteps-1)):
self.canvas.create_line(0, (num+1)*dist, self.width, (num+1)*dist)
def drawcells(self, event):
cell = getcords(event.x, event.y)
if cell not in celstate:
self.fillrects([cell])
celstate.add(cell)
def killcell(self, event):
cell = getcords(event.x, event.y)
if cell in celstate:
celstate.remove(cell)
col, row = cell
tag = "{},{}".format(col, row)
obj.canvas.delete(tag)
def fillrects(self, cords):
for gcords in cords:
col, row = gcords
tag = "{},{}".format(col,row)
dist = self.dist
self.canvas.create_rectangle(col*dist, row*dist, (col+1)*dist, (row+1)*dist,
fill="black", tags=(tag, "rect"))
root = tk.Tk()
obj = GUI(root, width, height, dist)
root.mainloop()
The code works as following:
I only save the cells that are alive in the celstate set.
I then find the deadcells that could become alive and iterate over the dead and alive cells in the
If the celstate is the same as the previous or theres no alive cells: then the function getnewstate returns None.
In the start method I then call the function getnewstate and draw its content until celstate returns None (with the function startloop that calls itself with the after method).
I dont understand why "loop finished" can be printed if startloop hasnt stop yet.
Even though I dont understand this part the code still works as intended which just makes it more anoyingly mistirious for me.
Can anyone help clarify whats going on??
The dist variable represents the cell size in pixels
You can draw new cells using the left button of the mouse or erase existing ones using the right button. (The cool part is that you can do that while the animation is still going)
I'm sure the problem comes because I dont really understand how the mainloop works
The tkinter after method effectively sends a message to the mainloop() to run the callback function in n milliseconds. Your start function sends this message then prints "loop finished". It doesn't wait for the after callback to return before carrying on execution. 100 ms later it calls startloop() and recalculates and displays the new grid. If it did wait for the callback to return it would freeze the UI while it waited. The after function lets you run code after a delay but still have an active ui.
I've amended your start function to print "loop finished" instead of returning None on your exit parts of the code.
def start(self, event):
if event.keycode == 32 or event.keycode == 13:
def startloop():
newstate = getnewstate()
if newstate == None:
print("loop finished")
elif newstate == "":
self.canvas.delete("rect")
print("loop finished")
else:
self.canvas.delete("rect")
self.fillrects(list(newstate))
self.master.after(100, startloop)
startloop()
One problem you may have is that the game of life can reach stable conditions that return to the same sate every two cyles. Some shapes have even longer cycle periods.
HTH

How to import parameters of a different class

I have two files cointoss and flibbell, python programs. I have to import cointoss into flibbell so that flibbell can inherit the number of flips given by the user. The flibbell will then stimulate multiple cointosses in a tkinter GUI. The cointoss file must have a main() so when I tried to run cointoss without if __name__ == "__main__":, it works but when I run the cointoss file using CoinToss() which is the class, it gives me error saying TypeError: __init__() missing 1 required positional argument: 'flips'
When I then try to run flipbell.py with cointoss inherited, it gives me an error saying it does not recognize the CoinToss init variable.
Here is the cointoss file:
from random import randint
import random
class CoinToss:
def __init__(self,flips):
self.state = 1
self.numb = flips
def __str__(self):
firstline = 'The ball at the start : ball: %d, state : %d, value : %d' % (0,0,self.numb)
return firstline
def flip(self):
rand_value = randint(0, 1)
if rand_value == 1:
self +=1
else:
self -=1
return self
def main():
flips = int(input("give the number of flips: "))
dxp = CoinToss(flips)
print(dxp)
k = 0
value_change = flips
for i in range(1,(flips*2) +1):
flips = value_change
value_change = CoinToss.flip(flips)
print('after: ball: %d, state: %d, value: %d' % (i, k , value_change))
k = k+1
if __name__ == "__main__":
main()
When I run the cointoss file alone, this is the result I am supposed to get:
python cointoss.py
Give the number of flips : 6
The ball at the start : ball : 0, state : 0, value : 3
after flip 1, ball : 0, state : 1, value : 2
after flip 2, ball : 0, state : 2, value : 3
after flip 3, ball : 0, state : 3, value : 2
after flip 4, ball : 0, state : 4, value : 3
after flip 5, ball : 0, state : 5, value : 2
after flip 6, ball : 0, state : 6, value : 1
And here is the flipbell file:
from tkinter import Tk, Canvas, Button, W, E
import random
from RRcointossRR import *
class FlipBell(CoinToss):
"""
GUI to simulate billiard ball movement.
"""
def __init__(self, wdw, increment, delay):
super(FlipBell,self).__init__(num_flip)
"""
Determines the layout of the GUI.
wdw : top level widget, the main window,
dimension : determines the size of the canvas,
increment : step size for a billiard move,
delay : time between updates of canvas.
"""
self.ballList = []
wdw.title('Coin flips and Bell Curve')
self.dim = num_flip # dimension of the canvas
self.inc = increment
self.dly = delay
self.togo = False # state of animation
# initial coordinates of the ball
self.xpt = self.dim//2
self.ypt = 0
self.cnv = Canvas(wdw, width=self.dim,\
height=self.dim, bg='white')
self.cnv.grid(row=0, column=0, columnspan=2)
self.bt0 = Button(wdw, text='start',\
command=self.start)
self.bt0.grid(row=1, column=0, sticky=W+E)
self.bt1 = Button(wdw, text='stop',\
command=self.stop)
self.bt1.grid(row=1, column=1, sticky=W+E)
self.points = {}
self.end_points = {}
self.value = 0
def placecoin(self, xpt, ypt):
self.cnv.create_oval(xpt-1, ypt-1, xpt+1, ypt+1,\
width=2, outline='red', fill='red', tags='coin')
def drawball(self,ball):
"""
Draws the ball on the canvas.
"""
ball.x = ball.value[1][self.points[ball]]
ball.y += ball.dy
self.cnv.create_oval(ball.x-1, ball.y-1, ball.x+1, ball.y+1,\
width=1, outline='black', fill='red', tags='dot')
if ball.y == self.dim:
print(ball.x)
print(ball.y)
if self.end_points[ball.x]>=1:
print("inside 1")
self.placecoin(ball.x,ball.y-self.end_points[ball.x])
else:
self.placecoin(ball.x,ball.y)
self.end_points[ball.x]+=2
self.ypt = 0
def add(self): # Add a new ball
a=Ball(self.dim)
self.ballList.append(a)
self.points[a] = 0
def animate(self):
"""
Performs the animation.
"""
for i in range(1,(self.dim)+1):
self.end_points[i] = 0
while self.togo:
self.add()
self.cnv.delete('dot')
self.value+=1
for ball in self.ballList:
self.drawball(ball)
self.points[ball]+=1
if self.points[ball] == self.dim:
self.ballList.remove(ball)
self.value = 0
self.cnv.update()
def start(self):
"""
Starts the animation.
"""
self.togo = True
self.animate()
def stop(self):
"""
Stops the animation.
"""
self.togo = False
class Ball(FlipBell):
ball = 0
def __init__(self, dimension):
self.dim = dimension
self.x = (self.dim)//2 # Starting center position
self.y = 0
self.dx = 10 # Move right by default
self.dy = 1# Move down by defaultaa
self.value = CoinToss.main()
Ball.ball += 1
def main():
"""
Defines the dimensions of the canvas
and launches the main event loop.
"""
top = Tk()
# dimension of canvas
increment = 10 # increment for coordinates
delay = 1 # how much sleep before update
num_flips = 3
num_value = dimension//2
FlipBell(top, increment, delay)
top.mainloop()
if __name__ == "__main__":
main()
Also, I need to change value in self.value = CoinToss() located in Class Ball(FlipBell): according to the CoinToss main, is that the correct way to do it?
Your problem is that, because num_flip isn't defined globally (which it shouldn't be), your super() method can't find any variable called num_flip. This number needs to be passed into the subclass's __init__, and then passed to the parent class. See:
class FlipBell(CoinToss):
"""
GUI to simulate billiard ball movement.
"""
def __init__(self, wdw, increment, delay, num_flip):
super(FlipBell,self).__init__(num_flip)
or more commonly and generally:
class FlipBell(CoinToss):
"""
GUI to simulate billiard ball movement.
"""
def __init__(self, wdw, increment, delay, *args, **kwargs):
super(FlipBell,self).__init__(*args, **kwargs)

tkinter cant assign text variable

the problem was my input popup had to be a toplevel window so changing tk.Tk() to tk.Toplevel() makes it work fine ;)
#Josh Harrison
#3008088
from graphics import *
from random import randrange
import winsound, sys
#scale for size of squares
scale = 50
#setupBoard sets up the board with a randomly generated puzzle
def setupBoard(size, color):
board = [[[0, Rectangle(Point(scale*.05,scale*.05),Point(scale*.95,scale*.95))] for x in range(size)] for x in range(size)]
for x in range(size):
for y in range(size):
board[x][y][1] = Rectangle(Point(x*scale+scale*.05,y*scale+scale*.05),Point(x*scale+scale*.95,y*scale+scale*.95))
for i in range(1):
selectTile(board, Point(randrange(size)*scale,randrange(size)*scale), size, color)
return board
#selectTile does the action for selecting tiles
#set color to 0 for black and white and 1 for color rotation
def selectTile(board,point,size,color):
#sets value switch according to if colors are desired or not
if color == 1:
valueSwitch = colorSwitch
else:
valueSwitch = bwSwitch
x = int(point.getX()/scale)
y = int(point.getY()/scale)
#temp is made to preserve the selected tiles state
temp = board[x][y][0]
#swap all square values
#note try and except are also looped
for i in range(3):
for z in range(3):
try:
board[x-1+i][y-1+z][0] = valueSwitch(board[x-1+i][y-1+z][0])
except:
#overlap fix for x maxed
if x == size - 1 and y != size - 1:
board[0][y-1+z][0] = valueSwitch(board[0][y-1+z][0])
#overlap fix for y maxed
if y == size - 1 and x != size - 1:
board[x-1+i][0][0] = valueSwitch(board[x-1+i][0][0])
#overlap fix for bottom right corner
if x == size - 1 and y == size - 1:
board[0][0][0] = valueSwitch(board[0][0][0])
for a in range(2):
board[0][size-a-1][0] = valueSwitch(board[0][size-a-1][0])
board[size-a-1][0][0] = valueSwitch(board[size-a-1][0][0])
#give middle square initial value again
board[x][y][0] = temp
#updateBoard updates the squares to the right colour according to value
def updateBoard(board, size, count):
if count != 0:
winsound.Beep(333, 200)
for x in range(size):
for y in range(size):
if board[x][y][0] == 0:
board[x][y][1].setFill('white')
elif board[x][y][0] == 1:
board[x][y][1].setFill('yellow')
elif board[x][y][0] == 2:
board[x][y][1].setFill('green')
elif board[x][y][0] == 3:
board[x][y][1].setFill('blue')
elif board[x][y][0] == 4:
board[x][y][1].setFill('black')
#drawBoard draws the initial board
def drawBoard(size, board, win):
for x in range(size):
for y in range(size):
board[x][y][1].draw(win)
return
#checks to see if board is white(Winning condition)
def winGame(board, size):
#steps through all x and y values
for x in range(size):
for y in range(size):
if board[x][y][0] != 0:
return 0
#returns true if no black squares are found
return 1
#valueSwitch() just makes switching values easier by checking values and selecting the appropriate one
def colorSwitch(value):
if value == 4:
return 0
else:
return value + 1
#bwSwitch only selects from black and white value
def bwSwitch(value):
if value == 4:
return 0
else:
return 4
#winMessage() displaying a winning message graphic
def winMessage(size, scale, win):
gameMessage = Text(Point(size*scale/2,size*scale/2),"You have won logic!")
gameMessage.setSize(int(scale/4))
gameMessage.setTextColor('red')
gameMessage.draw(win)
#gameMenu() is a menu to select game size
def gameMenu():
win = GraphWin("Logic Menu", 400, 600)
win.setBackground('light blue')
board = [[[Text(Point(0,0),'bleh'),Rectangle(Point(0,0),Point(200,200))] for y in range(3)] for x in range(2)]
#Making and drawing the buttons ;)
for x in range(2):
for y in range(3):
board[x][y][1] = Rectangle(Point(x*200+200*.05,y*200+200*.05),Point(x*200+200*.95,y*200+200*.95))
board[x][y][1].draw(win)
board[0][0][0] = Text(board[0][0][1].getCenter(), 'Click for 5x5 puzzle')
board[1][0][0] = Text(board[1][0][1].getCenter(), 'Click for 7x7 puzzle')
board[0][1][0] = Text(board[0][1][1].getCenter(), 'Click for 9x9 puzzle')
board[1][1][0] = Text(board[1][1][1].getCenter(), 'Click for 12x12 puzzle')
board[0][2][0] = Text(board[0][2][1].getCenter(), 'Click to toggle colors')
board[1][2][0] = Text(board[1][2][1].getCenter(), 'Highscores!')
#drawing button options
for x in range(2):
for y in range(3):
board[x][y][0].draw(win)
#check to see what button is pressed
point = win.getMouse()
x = int(point.getX()/200)
y = int(point.getY()/200)
#colors is either 1 for colors or 0 for no colors
colors = 0
#turning colors on and off
#board[0][2][1] is the rectangle for colors
while y == 2:
if x == 0:
if colors == 0:
winsound.Beep(400, 200)
colors = 1
board[0][2][1].setFill('green')
else:
winsound.Beep(363, 200)
colors = 0
board[0][2][1].setFill('')
else:
winsound.Beep(400, 200)
board[1][2][1].setFill('red')
#board is just passed in for a smother button click effect not necessary for functionality
highscore_board(board)
point = win.getMouse()
x = int(point.getX()/200)
y = int(point.getY()/200)
board[x][y][1].setFill('red')
winsound.Beep(400, 200)
win.close()
if x == 0 and y == 0:
return 5 , colors
if x == 1 and y == 0:
return 7 , colors
if x == 0 and y == 1:
return 9 , colors
if x == 1 and y == 1:
return 12 , colors
return 5 , colors
#highscore() checks to see if player has highscore and outputs a highscore to a text file
def highscore(count):
#checks to see if highscore file exists
try:
scoreInfo = [line.strip() for line in open('highscore.txt')]
#remove all spacing
for i in range(scoreInfo.count('')):
scoreInfo.remove('')
scoreInfo[1]
scores = int(len(scoreInfo)/2)
newEntry = 0
#creates new highscore file is none exist
except:
win = GraphWin("Highscore!", 400, 200)
gameMessage = Text(Point(200,100),"Please input name: ")
gameMessage.setSize(int(scale/4))
gameMessage.setTextColor('red')
gameMessage.draw(win)
name=inputWin()
f = open('highscore.txt', 'w')
f.write(name)
f.write('\n'+str(count))
f.close()
gameMessage.setText(name+': '+str(count)+' - saved!')
time.sleep(1)
win.close()
return
#if there is a new highscore it is added at the beginning of the file
for i in range(scores):
if scores < 10 or count < int(scoreInfo[i*2+1]):
win = GraphWin("Highscore!", 400, 200)
gameMessage = Text(Point(200,100),"Please input name: ")
gameMessage.setSize(int(scale/4))
gameMessage.setTextColor('red')
gameMessage.draw(win)
name=inputWin()
f = open('highscore.txt', 'w')
#max 10 highscores 9 + new highscore
if scores >= 10:
scores = 9
for i in range(scores):
try:
if count < int(scoreInfo[i*2+1]) and not newEntry:
f.write(name)
f.write('\n'+str(count))
f.write('\n\n\n')
newEntry = 1
f.write(scoreInfo[i*2])
f.write('\n')
f.write(scoreInfo[i*2+1])
f.write('\n\n\n')
except:
pass
#if no entries have been added
#the new value is then added to the end
if newEntry == 0:
f.write(name)
f.write('\n'+str(count))
f.write('\n\n\n')
f.close()
gameMessage.setText(name+': '+str(count)+' - saved!')
time.sleep(1)
win.close()
break
pass
#board is just passed in for a smother button click effect not necessary for functionality
def highscore_board(board):
win = GraphWin("Highscores", 200, 500)
win.setBackground('light green')
try:
scoreInfo = [line.strip() for line in open('highscore.txt')]
#remove all spacing
for i in range(scoreInfo.count('')):
scoreInfo.remove('')
Text(Point(50,20),"Highscores:").draw(win)
for i in range(10):
Text(Point(10,45*i+60),str(i+1)+'. ').draw(win)
try:
Text(Point(60,45*i+60),scoreInfo[i*2]).draw(win)
Text(Point(170,45*i+60),scoreInfo[1+i*2]).draw(win)
except:
pass
except:
Text(Point(100,250),"no scores yet.").draw(win)
time.sleep(.05)
board[1][2][1].setFill('')
#prevent program crash if scoreboard is exited through os
try:
win.getMouse()
winsound.Beep(363, 200)
win.close()
except:
winsound.Beep(363, 200)
import tkinter as tk
def getString(ment,mGui):
global hsname
hsname = ment.get()
mGui.destroy()
mGui.quit()
def inputWin():
mGui = tk.Tk()
ment = tk.StringVar()
mGui.title('New Highscore!')
mEntry = tk.Entry(mGui,textvariable=ment).pack(side=tk.LEFT)
mbutton = tk.Button(mGui,text='OK',command=lambda:getString(ment,mGui),fg='red',bg='blue').pack(side=tk.RIGHT)
mGui.mainloop()
return hsname
this is the portion that wont work
import tkinter as tk
def getString(ment,mGui):
global hsname
hsname = ment.get()
mGui.destroy()
mGui.quit()
def inputWin():
mGui = tk.Tk()
ment = tk.StringVar()
mGui.title('New Highscore!')
mEntry = tk.Entry(mGui,textvariable=ment)
mEntry.pack(side=tk.LEFT)
mbutton = tk.Button(mGui,text='OK',command=lambda:getString(ment,mGui),fg='red',bg='blue')
mbutton.pack(side=tk.RIGHT)
mGui.mainloop()
return hsname
I'm just screwing around trying to make adding a highscore more visual for a game I made this code it works fine by itself but when I import it or even copy the whole code into a py file with other functions it just stops assigning ment any values I don't understand :/
any help is appreciated
this is the code that runs the game
#Josh Harrison
#3008088
from logic_game import *
def playGame():
option = gameMenu()
size = option[0]
color = option[1]
win = GraphWin("Logic Game", size*scale, size*scale)
win.setBackground('light pink')
board = setupBoard(size, color)
drawBoard(size, board, win)
countText = Text(Point(scale,scale/2),'moves: 0')
countText.setTextColor('red')
countText.draw(win)
count = 0
while not winGame(board, size):
updateBoard(board, size, count)
selectTile(board, win.getMouse(), size, color)
count += 1
countText.setText('moves: ' + str(count))
updateBoard(board, size, count)
winMessage(size, scale, win)
highscore(count)
#pauses the window and waits for click before continuing
win.getMouse()
#closes the window "win"
win.close()
playGame()
link for graphics.py
http://mcsp.wartburg.edu/zelle/python/graphics.py
I don't understand exactly what your problem is doing wrong, but I'm pretty sure I know what the problem is.
Most of your program is using some library named graphics to run a GUI. Then you're trying to use Tkinter to run another GUI in the same program.
I don't know what that graphics library that is, but unless it's either built on top of Tkinter, or specifically designed to work with Tkinter, this is unlikely to work. Both of them are going to try to be in charge of the one and only GUI for your program, handling all of the events from the user/windowing system, and so forth. One or both are going to fail.
In fact, even if graphics were built on top of Tkinter or designed to work together with it, calling mainloop on the Tkinter window is at best going to freeze up the rest of your GUI until you exit that mainloop, and at worst going to break the outer mainloop that the other GUI is relying on.
From what I can see from your other code, that graphics library seems to have enough features to do everything you were trying to do with Tkinter—create a new window, place some widgets on it, handle a button click. So, why not just use that?
Now that you've given us a link to the graphics library you're using… it looks like a thin wrapper around Tkinter. Which means you should be able to integrate them easily. You just have to create a new Toplevel instead of a root window (since graphics has already created a Tkinter root), and not call mainloop or quit (because you're already in a Tkinter main loop created by graphics).
Since you haven't given us an SSCCE that I can just run and hack on, I've built my own super-simple one around the first example in the graphics docs, which does what you were trying to do, and also shows how you can interact with the graphics window from the Tkinter code.
from graphics import *
import Tkinter as tk
def getString(ment,mGui):
global win
print(ment.get())
mGui.destroy()
win.close()
def inputWin():
global hsname
mGui = tk.Toplevel()
ment = tk.StringVar()
mGui.title('New Highscore!')
tk.Entry(mGui,textvariable=ment).pack(side=tk.LEFT)
tk.Button(mGui,text='OK',command=lambda:getString(ment,mGui),fg='red',bg='blue').pack(side=tk.RIGHT)
win.getMouse()
def main():
global win
win = GraphWin("My Circle", 100, 100)
c = Circle(Point(50,50), 10)
c.draw(win)
win.getMouse() # Pause to view result
inputWin()
main()
It would be better to refactor this to eliminate your global variables, either using an OO design (create a class so you can store things as instance attributes) or a functional design (pass values through closures or bake them in with lambda/partial, as you're already doing in your Button command), but I tried to follow the style you already set in your code rather than rewrite everything.

"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